Skip to content

feat: provider-runtime v1.8.2 — set Ready on every successful Observe - #31

Merged
braghettos merged 3 commits into
mainfrom
feat/ready-contract-v1.8.0
Nov 4, 2026
Merged

braghettos merged 3 commits into
mainfrom
feat/ready-contract-v1.8.0

Conversation

@braghettos

@braghettos braghettos commented Nov 4, 2026 •

Copy link
Copy Markdown

Warning

Do not release or roll this to a cluster until core-provider 2.13.56 is live on that cluster. That's the core-provider rollup release that maps NotObserved (krateo-platformops/core-provider#310). It's released, but a cluster still on an older core-provider would read the new Ready=Unknown/NotObserved wrong. Merging is fine. Cutting a git-provider release or rolling it to a cluster is not, until 2.13.56 is live there.

The contract

This PR bumps github.com/krateo-platformops/provider-runtime from v1.7.2 to v1.8.2. v1.8.0 makes Ready the provider's to set (runtime docs/overview.md, "The Ready contract"):

  1. Every Observe that returns a nil error sets Ready, including the ResourceExists: false and ResourceUpToDate: false paths. v1.8.1 makes one exception: a resource being deleted. The runtime's deletion branch sets Ready=Deleting itself, so a successful Observe owes nothing there. Setting it anyway is allowed.
  2. When Connect or Observe fails, the runtime sets Ready=Unknown/NotObserved. There are two exceptions. If the error is wrapped with reconciler.ReadyAssessed ("I set Ready myself in this call"), the runtime leaves Ready as the provider set it. This is checked first. Otherwise, if the resource is being deleted, the runtime sets Ready=Deleting (since v1.8.2).
  3. If a successful Observe leaves NotObserved in place, the runtime keeps it and emits a ReadyNotSetAfterObserve Warning. Since v1.8.1 this never happens for a resource being deleted.

Bumping alone would leave some paths stuck on NotObserved. For example, a drifted LocalResource with syncEnabled: false would stay NotObserved forever, because Update does nothing there. That's why the bump and the fixes come in one PR. v1.8.0 through v1.8.2 change only this contract (provider-runtime#22, #23, and v1.8.2's Deleting-on-error).

Paths changed

An Observe error while being deleted

Since v1.8.2, the runtime answers a plain Connect or Observe error on a resource being deleted with Ready=Deleting, so neither controller handles it. The runtime checks ReadyAssessed first, so it leaves a ReadyAssessed Ready alone even during deletion. Repo's unreachable-commit error is the one such error (failSync sets Unavailable). For a Repo being deleted, that call site now sets Deleting over it (repo.go:233). Deleting holds whatever the target answers. With Deleting on the object, the next Observe takes the existing Ready=Deleting shortcut and the runtime releases the object. Neither controller removes anything from the target on delete (Delete is a no-op), so nothing is skipped.

Repo (internal/controllers/repo/repo.go)

Path Observation Ready Why
Deletion after a ReconcileError, delete allowed (:152) exists=false Deleting Not required since v1.8.1. It's set anyway so that every path taken while deleting reports the same Ready.
Nothing pushed yet, no external name (:172) exists=false Unavailable ("the target repository has not been synced yet") There's nothing to use yet. Create, when the policy allows it, moves it to Creating. This follows the runtime's greeting-controller example.
Pushed commit no longer reachable on the target branch, enableUpdate=true (:240) exists, not up to date Unavailable (names the commit, branch and URL) The target no longer holds what this Repo pushed. This matches the enableUpdate=false branch, which already reports Unavailable through failSync.
Origin ahead of the last sync, enableUpdate=true (:255) exists, not up to date Available The target still holds the last sync and is usable. ResourceUpToDate=false carries the drift. This matches the enableUpdate=false branch, which already falls through to Available. Before, this path relied on Update setting Creating, but Update does nothing when the policy blocks updates, and the reconciler skips Update under !ShouldUpdate.
Pushed commit unreachable, enableUpdate=false (:236) error Unavailable, kept failSync sets Unavailable + ReconcileError and returns the error. That's a finding, not a failure to observe: the target was read and the commit isn't on it. The error is now wrapped with reconciler.ReadyAssessed, so Unavailable survives and the message is unchanged (#23). On a Repo being deleted, Deleting replaces it (see above).

The other paths are unchanged. The Ready=Deleting early return (:138) has nothing new to report. Up to date and "origin ahead, enableUpdate=false" already set Available (:264). The remote-read errors (:189, :202) stay plain, because they mean "could not tell" and NotObserved is the right answer there.

LocalResource (internal/controllers/localresource/localresource.go)

Path Observation Ready Why
Deletion after a ReconcileError, delete allowed (:199) exists=false Deleting Same as Repo.
An older spec of this LocalResource is committed, none matches the current one (:246) exists, not up to date Available The target holds an older version of the file. ResourceUpToDate=false carries the drift. Update commits the current spec, unless syncEnabled: false or the policy blocks updates. In those cases the file stays as it is, and so does Available.
Nothing committed yet (:264) exists=false Unavailable ("the resource has not been committed to the target repository yet") Same as Repo.

The rest is unchanged. The up-to-date path, and the path where Create recorded the branch head without a footer commit, already set Available (:271). No LocalResource error follows a Ready write, so nothing there is wrapped.

Connect

Both controllers read secrets in Connect. A Connect failure (for example a missing credentials secret) now shows Ready=Unknown/NotObserved next to Synced=False/ReconcileError, where Ready used to keep its last value. That's intended (contract rule 2), so Connect is unchanged.

Tests

  • internal/clients/git/gittest: a new test-only helper that creates git repositories in t.TempDir(), so Observe reads real history from a local path. It needs no network or git server, but git must be on PATH, because go-git serves a local path through git-upload-pack.
  • repo/observe_ready_test.go:
    • TestObserveSetsReady calls reconcilertest.AssertObserveSetsReady once per success path: deletion, not synced, unreachable commit with updates, origin ahead with and without updates, up to date. Each case first checks that Observe returns the ExternalObservation for the path it names, then checks the Ready reason.
    • TestObserveUnreachableTargetCommitIsReadyAssessed: the error is IsReadyAssessed, its message is unchanged, Ready=False/Unavailable, Synced=ReconcileError.
    • TestObserveUnreadableRemoteIsNotReadyAssessed: a could-not-tell error stays plain.
    • TestObserveUnreachableTargetCommitWhileDeletingSetsDeleting: on a Repo being deleted, the error stays ReadyAssessed and Ready=Deleting. That's the one deleting case the runtime doesn't cover. Plain errors during deletion are covered by provider-runtime's own tests.
  • localresource/observe_ready_test.go: the same pattern. Its cases are deletion, not committed, drifted with syncEnabled: false, up to date, and recorded without a footer, plus the plain-error check.

On main's controller code, the new tests fail on exactly the paths this PR changes and pass on the rest. With the Deleting write at repo.go:233 removed, the deleting test fails.

Verification

Check Result
go build ./... pass
go vet ./... and go vet -tags=unit,integration ./... pass
go test -race -count=1 ./... pass, all packages ok
CI's integration tests (-tags=integration) not run locally: they create a kind cluster. CI runs them on this PR.

No Makefile in this repo. No release is cut by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_016RAxvnK22nQGNMCWkPMj9z

braghettos and others added 3 commits November 4, 2026 08:44
provider-runtime v1.8.0 makes Ready the provider's to set. When Connect or
Observe fails, the runtime sets Ready=Unknown/NotObserved, unless Ready is
Deleting or the error is wrapped with reconciler.ReadyAssessed. Every
successful Observe must then set Ready again, or NotObserved stays and the
runtime emits a ReadyNotSetAfterObserve Warning.

Repo Observe:
- deletion after a ReconcileError: Deleting (the object is being released)
- nothing pushed yet: Unavailable
- pushed commit no longer on the target branch, enableUpdate=true:
  Unavailable, as on the enableUpdate=false branch
- origin ahead, enableUpdate=true: Available; the drift is carried by
  ResourceUpToDate=false
- the enableUpdate=false unreachable-commit error is wrapped with
  ReadyAssessed, so the Unavailable failSync sets survives

LocalResource Observe:
- deletion after a ReconcileError: Deleting
- nothing committed yet: Unavailable
- an older spec committed: Available, also when syncEnabled=false makes
  Update a no-op

Tests call reconcilertest.AssertObserveSetsReady once per success path,
against local git repositories (new internal/clients/git/gittest), and
check the ReadyAssessed error and that a could-not-tell error stays plain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RAxvnK22nQGNMCWkPMj9z
…s during deletion

v1.8.1 stops requiring Ready from a successful Observe on a resource
being deleted, because the runtime's deletion branch sets Deleting
itself. The Deleting writes on the deletion returns stay: they are
allowed and keep every path taken while deleting on the same Ready.

That exemption covers successes only. On an Observe error the runtime
writes NotObserved unless Deleting is already on the object. So Observe
now wraps the old body (observe) and sets Deleting before returning an
error for a resource being deleted. This includes the unreachable-commit
error, where failSync had set Unavailable. Tests cover both controllers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RAxvnK22nQGNMCWkPMj9z
…ndant

v1.8.2 sets Ready=Deleting itself when Connect or Observe fails on a
resource with a deletion timestamp. That makes the observe() wrapper,
which set Deleting before any such error, redundant, so it is removed
from both controllers along with LocalResource's test of it.

One case is still the provider's: the runtime checks ReadyAssessed
before deletion, so it leaves a ReadyAssessed Ready alone even on a
resource being deleted. Repo's unreachable-commit error is ReadyAssessed
(failSync set Unavailable), so that call site now sets Deleting itself
when the Repo is being deleted. Its test is kept, narrowed to this case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RAxvnK22nQGNMCWkPMj9z
@braghettos braghettos changed the title feat: provider-runtime v1.8.1 — set Ready on every successful Observe feat: provider-runtime v1.8.2 — set Ready on every successful Observe Nov 4, 2026
@braghettos
braghettos merged commit b83928d into main Nov 4, 2026
3 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.

1 participant