Skip to content

Header chip and GitOps write guard read the manager answer - #2341

Merged
nadaverell merged 64 commits into
mainfrom
ownership/manager-chip-guard-main
Oct 26, 2026
Merged

nadaverell merged 64 commits into
mainfrom
ownership/manager-chip-guard-main

Conversation

@nadaverell

Copy link
Copy Markdown
Contributor

This is #2325's change, reopened against main. #2325 merged into its stacked base branch after #2320 had already merged, so none of it reached main. Same code as #2325's final head, with current main merged in, plus shorter docs: docs/ownership.md holds the manager-answer rules (states as a table), the docs/mcp.md section keeps what an agent needs, and the docs/gitops.md write-warning rules are one table of answer by operation.

Problem

The header chip and the GitOps write guard decide "which manager owns this" in the browser:

  • the chip uses the first tracking label it finds, inherits a parent's manager one hop up, and guesses an Argo CD Application's namespace by name;
  • the write guard trusts whatever owner the browser sends and never checks the managers' status lists. With no owner it says nothing.

So the chip and the warning could name a manager whose list doesn't include the object, or miss a second manager whose list does.

Change

Chip. When the server sends relationships.ownership.manager (#2319), the chip reads it: no browser inheritance, no guess by name. With an older Radar backend the chip behaves exactly as before; Radar Hub runs the newest UI against older Radars.

state chip tooltip
agree Managed by X the list naming it, and that its tracking metadata agrees
listed Listed by X the list naming it; a list alone can name an object its manager doesn't sync (a copy carrying another object's tracking ID, a CRD a HelmRelease skips)
via an ancestor same wording, plus "via " "Through Deployment â€Ķ", plus the object's own weak claims
claimed Tracked by X why the claim couldn't be confirmed: omitted, unreadable, remote, missing, withheld, â€Ķ
disagree Tracked by A, with a warning A's list doesn't name it; B's does
shared Listed by A and B no single manager's sync status shown
unknown / none no chip —

With several candidates (disagree, shared, several claims) the chip opens a menu naming each and what names it (its status, its metadata or both); with one it opens that manager. A chip without a link is focusable, so keyboard users reach its tooltip.

Related Resources' Managed By group is hidden only when the chip says exactly what it would.

Write guard.

  • The server decides. POST /api/gitops/write-evidence computes the manager answer itself, as the caller, reading the live target's metadata, and returns it as manager. Its lead decides which manager's policy applies. The client's owner is only a hint when the server has no ownership answer for the kind. When the server computed one and couldn't finish (the object was replaced since the cache saw it, the cache isn't ready), managerUnavailable says why, and neither the hint nor the cached answer is used: the dialog warns and asks for an acknowledgement.
  • Every cached answer is checked live, including none and unknown: an object adopted since the cache saw it has a manager the cache doesn't. One request per guarded dialog. A failed request leaves a cached answer that names a manager unconfirmed (acknowledgement); a cached none stays quiet.
  • The classifier composes. The existing operation rules are unchanged: status writes say nothing, creating a child is informational, an operator-owned delete is informational. On top of them:
    • disagree, shared, or several claims: field writes get "may revert", naming every candidate, and no single candidate's ignore rules or sync policy exempt the change;
    • an unconfirmed claim (omitted, unreadable, remote, missing, withheld): can't prove "will revert" or waive the acknowledgement;
    • "will revert" needs the list and the object's metadata to agree (agree). A list alone (listed) gives "may revert", saying Radar can't confirm a sync applies the object, then the field evidence under "If one does:";
    • a CronJob's Job (scheduled, or made with kubectl create job --from) runs once: deleting it doesn't say its controller recreates it;
    • values from a newer Radar (a state, check, source or chain end this UI doesn't know) render generically and read as unconfirmed, so they never prove a revert, waive a warning or link a manager;
    • an adopted Helm install: a native Helm release's annotations beside an Argo CD Application or Flux Kustomization keep "may revert" even when that manager ignores the field, since the release's next upgrade or rollback can still overwrite it;
    • via an ancestor, with no claim of the object's own: informational ("generated from , which X manages", or "which X lists", or "whose metadata names X", as the evidence allows), no acknowledgement;
    • via, with the object's own claim: keeps "may revert";
    • a chain Radar couldn't read (owner withheld, unknown or unresolvable, a loop, too long): keeps the conservative "couldn't tell" warning. An owner that is gone or replaced ends the chain with an answer and adds no warning;
    • no manager in the evidence (older Radar): verdicts as before.

Verification

  • Tests. Classifier tests cover each rule above, including:

    • an ignore rule on one of two managers still warning;
    • a status write on a shared object staying silent;
    • gone and replaced owners not warning.

    Chip tests cover every state's wording, plural names, and the group-hiding rule. Hook tests check the evidence query fires for ambiguous and inherited answers and that the server's answer wins over the client's. Server tests check manager in the response and that the server's lead overrides a conflicting client owner. A live-overlay test covers live metadata, a replaced UID, a cache switch and restricted access.

  • Checks. make tsc is clean. Frontend: 5,290+ tests across the UI package, 64 hook tests in the app. Go: pkg/..., internal/k8s, internal/server, internal/mcp.

  • Live on a test cluster:

    • a relabelled Flux Pod reads "Tracked by flux-system/wg-ks" with a warning; the tooltip says wg-ks's status doesn't list it, wg-ks-susp's does, through its Deployment;
    • a Pod of a Deployment two Applications list reads "Listed by â€Ķ-a and â€Ķ-b";
    • an ordinary Argo CD Deployment reads "Managed by â€Ķ · Synced", and its Managed By group is hidden;
    • the disagreeing Deployment's Set image dialog warns that both Kustomizations may overwrite the fields and requires the acknowledgement;
    • the write-evidence endpoint answers a Pod through its Deployment's Application even when the client sends a different owner.

â€Ķlse winner

Radar names an object's GitOps manager from two kinds of evidence that can
disagree: managers' status lists, and the object's own tracking labels and
annotations. View.Manager weighs both for an object and, when its own
evidence doesn't settle it, for its nearest controller ancestor, and sums
them up as shared, disagree, agree, listed, claimed, unknown or none. A
disagreement stays one; none means no evidence in what Radar reads, not
unmanaged.

Claims are parsed in full, every candidate kept, a copied tracking ID
marked. The answer reaches the wire as relationships.ownership.manager;
existing fields are unchanged.
â€Ķanager answer

A Deployment copies a Helm release's annotations onto its ReplicaSets, so
on an object a controller owns that claim is inherited like an instance
label: it no longer stops the walk short of a list naming the Deployment.
A cluster-scoped object's tracking ID is compared without the namespace
Argo CD writes into it. An access check Radar couldn't make keeps an answer
from being none. The answer keeps the claims of the objects it walked past
and says which controller reference it couldn't follow.
A controller of a kind discovery can't map left the answer's stop without
the owner it names. The reference on the object the walk reached names it,
as the primary owner's does.
get_resource and diagnose now carry resourceContext.manager: which GitOps
managers' status lists name the resource, what its metadata claims and
what checking each claim found, the state they sum up to, and the
controller ancestor whose evidence answers when the resource's own
doesn't. The owner-only ownership answer resource contexts use now also
answers the manager: one recorder lookup per object on the controller
chain, still no search for children.
Checking a stale claim read every object the claimed manager's list names,
just to report that the list omits the claimant; a large Application made
that thousands of reads per answer.
â€Ķwhat none means

With via answering, the resource's own claims and those of the objects in
between now come through as alsoClaimed, each on its object: inherited
metadata that didn't settle the question isn't disproven. get_resource's
description says none is not unmanaged and disagree means check both; the
catalog budget rises by 50 bytes for it. The docs name the unchecked check
and the Helm precedence.
A tracking ID naming argocd/shop and an instance label naming shop made a
claimed answer with no lead, as if they named two managers; the one that
says its namespace now leads. An Application identity whose name holds an
underscore, which Kubernetes names can't, makes no claim.
A controller that is gone, was replaced or is an Event ends the chain: no
manager above it was missed, so an object nothing names reads none, not
unknown.
Only an owner Radar couldn't read or decide, or a looping or overlong
chain, leaves a manager above it unread and warrants the conservative
warning; a gone or replaced owner ends the chain with an answer.
A claim its manager's list confirms read as a second line about the same
manager; it now joins the list's line. Claims copied from another object,
as the tracking annotation a ReplicaSet inherits from its Deployment, add
nothing and are left out; a claim that disagrees is said first.
â€Ķount one Application once

A Pod managed through its Deployment now says via Deployment on the chip,
not only in the tooltip. An opt-out on the target's own annotations, as
Flux's reconcile: disabled, holds for an unconfirmed claim too. A tracking
ID and an instance label naming one Application are one candidate, not two.
A ReplicaSet carries its Deployment's Helm release annotations; with a
plain Helm Deployment they made each ReplicaSet edit warn as if a GitOps
tool might revert it, rather than note that it is generated from the
Deployment.
â€Ķ by list state

A Pod whose ReplicaSet's instance label names an Application, under a
Deployment with no evidence, answered none: the walk passed the only
claim it found. The nearest ancestor claim now answers, as claimed via
that ancestor, when the object's own metadata claims nothing.

Argo CD's annotation+label tracking cuts the instance label to 63
characters; such a label now names the tracking ID's Application instead
of a second one that doesn't exist.

Checking a claim against a manager whose list omits the object read and
sorted the whole list. ReadSession.Membership reads only the list's
state.
get_resource and diagnose added a warning naming the manager the object's
metadata claims beside a manager answer that weighs those claims against
the managers' lists; when they disagreed, the two said different things.
With a manager answer the metadata warning is left out.

Claims of objects the walk passed now reach alsoClaimed without Via too.
â€Ķt an unconfirmed answer

- A list alone (listed) can name an object its manager doesn't sync: a
  copy carrying another object's tracking ID, or a CRD a HelmRelease
  skips. It now gives may-revert, and the chip says Listed by.
- The write-evidence response says when the server couldn't answer what
  manages the live object (managerUnavailable); the client owner hint and
  the cached answer are then not used. A failed evidence request leaves a
  cached answer that names a manager unconfirmed.
- The dialog checks every cached answer against the live object,
  including none and unknown.
- A native Helm release's annotations beside an Argo CD or Kustomization
  lead mean an adopted install; that manager's exemption no longer waives
  the release's next upgrade or rollback.
- Generated-from-parent notes say what the evidence says of the parent.
- A chip with several candidates opens a menu naming each and what names
  it; a chip without a link is focusable, so its tooltip is reachable by
  keyboard. The remote reason says Radar can't confirm the destination.
â€Ķcopied tracking IDs

The manager answer covers Argo CD, Flux and Helm metadata, not a
controller named only by app.kubernetes.io/managed-by (cert-manager,
operators): that warning is now kept beside the answer. A tracking ID
copied from another object no longer raises the warning on any path.
â€Ķidate

- A listed answer's reason no longer carries a will-recreate or
  will-overwrite promise under a may-revert verdict: it says the list
  names the object and a sync may apply it.
- Generated-from-parent notes for a parent the managers disagree on say
  which manager its metadata names and which lists it.
- The chip's menu names every candidate; one whose label names no
  namespace is shown without a link.
â€Ķname

A copied Argo CD tracking ID was detected first and then dropped, which
also hid an independent controller its labels name (cert-manager). The
detector now skips the copied ID and keeps looking. AnswerOwner's comment
says it answers the manager too.
A claimed answer whose only candidate had no lead (an unfamiliar state
from a newer Radar) or was a manager kind this UI doesn't map fell
through to no warning. It now asks for an acknowledgement like any
unconfirmed claim; a native Helm lead keeps its own rules.
The listed rewrite replaced the whole reason, dropping what the field
evidence said (an ignore rule that only affects comparison, a suspended
manager) and naming the Application twice. It now says once that Radar
can't confirm a sync applies the object, then gives the original reason
under 'If one does:'.
The answered path dropped the warning when the detector's first hit was a
claim the answer covers, hiding an independent controller label behind
it (an own Argo CD tracking ID beside a cert-manager label), and treated
Helm named by labels alone as covered though the answer only reads the
release annotation. The detector now lists every manager the metadata
names, marks the ones the answer weighs, and the answered path warns
about the first it doesn't. Resource-context comments say what Build
reads and what the compact answer keeps.
- Tracked by says up front that Radar hasn't confirmed the manager the
  metadata names; claims passed on the way show without via too.
- The manager's sync status shows on the chip only for agree, where it
  is this resource's manager; beside a list alone it read as the
  resource's own state.
- An informational note on a list-only answer says Listed by, as the
  chip does, not Managed by.
- The list-only warning leads with what Radar can't confirm, then the
  field evidence under 'If it does:'.
- A CronJob's Job delete says the CronJob can still create new Jobs.
When the answer came from an ancestor above the CronJob, the note said
that ancestor could create new Jobs on its schedule.
ownership.md holds the rules, with the states as a table. The MCP
section keeps what an agent needs and links there. The drawer chip
bullet says what each state shows, and the write warning's rules become
one table of answer by operation instead of two paragraphs.
@nadaverell
nadaverell requested a review from hisco as a code owner October 26, 2026 16:37
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Use manager answers for the header chip and GitOps write guard

âœĻ Enhancement 🐞 Bug fix 🧊 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

â€Ē Show GitOps managers from server ownership evidence instead of browser guesses, while retaining
 older-Radar behavior.
â€Ē Recheck ownership against the live target before write warnings and apply conservative verdicts
 when evidence is uncertain.
â€Ē Cover ambiguous managers, controller ancestry, access gaps, and compatibility with tests and
 consolidated documentation.
Diagram

graph TD
  View["Ownership view"] --> Answer["Manager answer"] --> Chip["Header chip"]
  Answer --> Hook["Write guard hook"] --> API["Write evidence API"] --> Live["Live target"]
  API --> View
  Hook --> Classifier["Write classifier"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Refresh full relationships before each write
  • ➕ Could reuse the existing relationships response without adding a manager answer to write evidence.
  • ➖ Adds a broader read to each dialog and separates ownership validation from the live target and field-policy evidence.

Recommendation: Keep the manager answer in the existing write-evidence response. It lets the server check live metadata and select policy together, while the cached relationships answer remains suitable for the chip. Preserve the legacy path for older Radars.

Files changed (19) +1368 / -119

Enhancement (6) +350 / -35
ownership.goAnswer management using live target metadata +42/-0

Answer management using live target metadata

â€Ē Adds a live-target overlay for ownership reads while retaining cached ancestor and recorder data. Refuses answers when the target was replaced or the read session becomes invalid.

internal/k8s/ownership.go

gitops_write_evidence.goReturn a server-computed manager in write evidence +30/-12

Return a server-computed manager in write evidence

â€Ē Computes ownership for the live target as the caller and uses its lead to select policy. Returns managerUnavailable when the answer cannot be completed, without falling back to the client's owner hint.

internal/server/gitops_write_evidence.go

ManagedByChip.tsxRender chips from structured manager answers +129/-2

Render chips from structured manager answers

â€Ē Adds evidence-based wording, tooltips, candidate menus, and links only to managers Radar found. Keeps the legacy owner chip and native Helm presentation available.

packages/k8s-ui/src/components/shared/ManagedByChip.tsx

WorkloadView.tsxUse manager answers in workload headers +17/-12

Use manager answers in workload headers

â€Ē Prefers the structured answer for GitOps and Helm chip selection and suppresses redundant Managed By groups only for exact direct matches. Falls back to existing owner resolution without an answer.

packages/k8s-ui/src/components/workload/WorkloadView.tsx

gitops-owner.tsProject manager evidence into chip candidates +129/-8

Project manager evidence into chip candidates

â€Ē Adds candidate, tooltip, navigation, and group-coverage helpers for manager answers. Retains the earlier managedBy projection when no answer is present.

packages/k8s-ui/src/utils/gitops-owner.ts

WorkloadView.tsxEnable navigation for manager-answer chips +3/-1

Enable navigation for manager-answer chips

â€Ē Provides the GitOps navigation callback when an ownership answer exists, including disagreements without a single lead.

web/src/components/workload/WorkloadView.tsx

Bug fix (3) +191 / -23
gitops-write-guard.tsCompose write verdicts with manager certainty +161/-11

Compose write verdicts with manager certainty

â€Ē Uses server answers to distinguish confirmed management from listings and claims, preventing uncertain evidence or one candidate's policy from granting reassurance. Preserves operation-specific rules and conservative handling of unread chains and unavailable live answers.

packages/k8s-ui/src/utils/gitops-write-guard.ts

useGitOpsWriteGuard.tsRecheck cached ownership for guarded writes +16/-3

Recheck cached ownership for guarded writes

â€Ē Requests write evidence whenever a cached manager answer exists, including none and unknown. Passes the server's returned answer to the classifier and resolved ownership state.

web/src/hooks/useGitOpsWriteGuard.ts

useResolvedGitOpsOwner.tsSkip browser ownership guesses when Radar answers +14/-9

Skip browser ownership guesses when Radar answers

â€Ē Uses the manager lead when present and bypasses parent inheritance and Argo namespace-name lookup. Keeps those resolution paths for older backends.

web/src/hooks/useResolvedGitOpsOwner.ts

Tests (7) +764 / -2
ownership_test.goTest live ownership overlay boundaries +75/-0

Test live ownership overlay boundaries

â€Ē Checks live metadata precedence, replacement and cache-switch refusal, and caller-specific access restrictions.

internal/k8s/ownership_test.go

gitops_write_evidence_test.goVerify server ownership overrides client hints +64/-0

Verify server ownership overrides client hints

â€Ē Tests that an agreeing server answer selects the correct owner and policy, while disagreeing or negative answers cannot be overridden by a client-supplied owner.

internal/server/gitops_write_evidence_test.go

ManagedByChip.manager.test.tsxTest manager-answer chip wording +108/-0

Test manager-answer chip wording

â€Ē Covers ownership states, claim explanations, multiple managers, ancestor wording, and when sync status may be shown.

packages/k8s-ui/src/components/shared/ManagedByChip.manager.test.tsx

ManagedByChip.menu.test.tsxTest manager menu and link safety +77/-0

Test manager menu and link safety

â€Ē Verifies candidate menus, keyboard focus for unlinked chips, and disabled navigation for unresolved, missing, or unfamiliar manager evidence.

packages/k8s-ui/src/components/shared/ManagedByChip.menu.test.tsx

WorkloadView.relationships.test.tsxTest drawer manager projection +35/-0

Test drawer manager projection

â€Ē Checks that Managed By disappears only when a direct, single recorder is fully represented by the chip. Also covers coverage notes, additional managers, ancestor answers, and native Helm.

packages/k8s-ui/src/components/workload/WorkloadView.relationships.test.tsx

gitops-write-guard.test.tsExercise ownership-aware write verdicts +294/-0

Exercise ownership-aware write verdicts

â€Ē Tests ambiguous and unconfirmed managers, ancestor and CronJob behavior, adopted Helm risk, live-check failures, unfamiliar values, and older-backend verdicts.

packages/k8s-ui/src/utils/gitops-write-guard.test.ts

useGitOpsWriteGuard.test.tsxTest live evidence fetching and answer precedence +111/-2

Test live evidence fetching and answer precedence

â€Ē Verifies requests for ambiguous, inherited, and cached-negative answers; server precedence; avoidance of browser namespace guesses; and lead-status fetching.

web/src/hooks/useGitOpsWriteGuard.test.tsx

Documentation (3) +63 / -59
gitops.mdDocument chip states and write-warning rules +16/-4

Document chip states and write-warning rules

â€Ē Explains how the drawer uses manager answers, when Related Resources remains visible, and how the write guard treats each ownership state. Clarifies live verification, uncertainty, adopted Helm installs, and older-Radar behavior.

docs/gitops.md

mcp.mdCondense MCP manager-answer guidance +15/-28

Condense MCP manager-answer guidance

â€Ē Summarizes the manager evidence agents receive from get_resource and diagnose, including states, ancestor answers, and coverage. Links to the full ownership rules instead of repeating them.

docs/mcp.md

ownership.mdCentralize manager-answer semantics +32/-27

Centralize manager-answer semantics

â€Ē Recasts the ownership states as a table and documents claim checks, ancestor traversal, incomplete reads, and the wire response.

docs/ownership.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) ðŸŽĻ UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Unknown managers can silence write warnings ✓ Resolved
Description
evaluateGitOpsWriteGuard keeps a single-candidate agree answer but converts an unfamiliar
manager kind to a null owner, leaving no warning branch to classify the write. When a newer Radar
names a manager kind this UI does not recognize, field writes and deletes fall through to none
without an acknowledgement.
Code

packages/k8s-ui/src/utils/gitops-write-guard.ts[R603-605]

+  const owner = unconfirmed ? null : manager ? manager.lead ? gitOpsOwnerFromRef(manager.lead) : null : input.owner ?? null
+  const helmRelease = unconfirmed ? null : manager
+    ? manager.lead?.kind === 'HelmRelease' && !manager.lead.group ? { namespace: manager.lead.namespace, name: manager.lead.name } : null
Evidence
The ref mapper returns null for unknown kinds, while candidate enumeration retains their refs. A
single-candidate agree answer misses the manager-specific warning branches; with no owner, Helm
release, or ownership error, the classifier returns none.

packages/k8s-ui/src/utils/gitops-owner.ts[48-61]
packages/k8s-ui/src/utils/gitops-owner.ts[85-97]
packages/k8s-ui/src/utils/gitops-write-guard.ts[453-460]
packages/k8s-ui/src/utils/gitops-write-guard.ts[603-612]
packages/k8s-ui/src/utils/gitops-write-guard.ts[484-520]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A manager answer with an unfamiliar lead kind can produce no write warning because it has no recognized owner.
## Fix Focus Areas
- packages/k8s-ui/src/utils/gitops-write-guard.ts[602-612]
- packages/k8s-ui/src/utils/gitops-write-guard.ts[453-520]
## Recommended Fix
Treat a positive manager answer whose lead cannot be classified as unconfirmed, producing a may-revert verdict and acknowledgement for applicable writes. Add a test with a single-candidate agree answer and an unfamiliar manager kind.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Listed objects can get a safe write verdict 🐞 Bug ≡ Correctness
Description
The listed postprocessing only changes verdicts already classified as may-revert or
will-revert, so it retains informational verdicts from a manager's opt-out or effective ignore
rule. A field write to an object named only by that manager's status can therefore avoid
acknowledgement even though the list-only answer has not established that the manager applies the
object.
Code

packages/k8s-ui/src/utils/gitops-write-guard.ts[R620-623]

+    if (manager?.state === 'listed' && !manager.via && (verdict.level === 'will-revert' || verdict.level === 'may-revert')) {
+      return {
+        write,
+        level: 'may-revert' as const,
Evidence
classifyPath returns info for both exemption cases before the new listed wrapper runs. The
wrapper's level condition excludes info, while the documented listed field-write outcome is
may-revert.

packages/k8s-ui/src/utils/gitops-write-guard.ts[303-323]
packages/k8s-ui/src/utils/gitops-write-guard.ts[617-625]
docs/gitops.md[312-320]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
List-only manager answers retain informational field verdicts produced by a single manager's opt-out or ignore rule.
## Fix Focus Areas
- packages/k8s-ui/src/utils/gitops-write-guard.ts[617-625]
- packages/k8s-ui/src/utils/gitops-write-guard.ts[303-323]
## Recommended Fix
Make the listed-state handling explicit for informational exemption verdicts as well as revert verdicts, preserving the intended list-only uncertainty. Test both an effective field ignore rule and an object reconciliation opt-out.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. New claim types can assert certain reverts ✓ Resolved
Description
evaluateGitOpsWriteGuard normalizes unfamiliar manager states but does not downgrade an agree
answer containing an unfamiliar claim source or check. If a newer Radar supplies such a claim with a
recognized owner and field evidence marked present, the existing self-heal path can still produce
will-revert rather than treating the unfamiliar evidence as unconfirmed.
Code

packages/k8s-ui/src/utils/gitops-write-guard.ts[R602-603]

+  const manager = answered && !MANAGER_STATES.includes(answered.state) ? { ...answered, state: 'claimed' as const } : answered
+  const owner = unconfirmed ? null : manager ? manager.lead ? gitOpsOwnerFromRef(manager.lead) : null : input.owner ?? null
Evidence
The wire type explicitly allows source and check values to grow. The evaluator checks only state for
familiarity, and a recognized owner can subsequently take the field-classification path that returns
will-revert under self-heal.

packages/k8s-ui/src/types/core.ts[782-793]
packages/k8s-ui/src/utils/gitops-write-guard.ts[256-275]
packages/k8s-ui/src/utils/gitops-write-guard.ts[333-359]
packages/k8s-ui/src/utils/gitops-write-guard.ts[587-613]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Only unfamiliar manager states are normalized, allowing unfamiliar claim values to support a certain write verdict.
## Fix Focus Areas
- packages/k8s-ui/src/utils/gitops-write-guard.ts[587-613]
- packages/k8s-ui/src/utils/gitops-write-guard.ts[615-642]
## Recommended Fix
Recognize supported claim sources and checks before treating an answer as confirmed. For unfamiliar values, retain the manager names but cap applicable verdicts at may-revert and require acknowledgement; test an agree answer carrying an unfamiliar claim value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. A live owner inherits stale link checks ✓ Resolved
Description
confirmedOwnership replaces the owner from live evidence but retains ownerVerified and
ownerObject from the previously resolved owner. If the live target names a different manager, the
image dialog can enable a link to that manager based on verification of the old one.
Code

web/src/hooks/useGitOpsWriteGuard.ts[155]

+      return { ...ownership, manager: answer, owner, rawOwner: owner, helmOwner, ownerMatchedByName: false }
Evidence
The resolution hook derives verification from its cached-owner query. Spreading that result into the
live-owner replacement leaves verification untouched, and the image dialog uses that flag to offer
navigation for the replacement owner.

web/src/hooks/useResolvedGitOpsOwner.ts[118-124]
web/src/hooks/useResolvedGitOpsOwner.ts[135-146]
web/src/hooks/useGitOpsWriteGuard.ts[150-156]
web/src/components/workload/WorkloadView.tsx[1010-1035]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Live ownership replacement retains verification and object data belonging to the cached owner.
## Fix Focus Areas
- web/src/hooks/useGitOpsWriteGuard.ts[150-160]
- web/src/components/workload/WorkloadView.tsx[1010-1035]
## Recommended Fix
When the live owner differs from the resolved owner, clear stale owner object, status, and verification fields until the replacement owner is verified, or resolve that owner separately. Cover an evidence response that changes the owner.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
5. Deletes skip the warning when the owner chain couldn't be read ✓ Resolved
Description
In classifyWrite, the branch manager.via && !directClaims.length returns info for a delete
before the code reaches the delete-specific chainUnread(manager) check. The metadata/spec path
runs chainUnread first, but the delete path does not. If the Go walk stops at an owner it can't
follow (stop unknown/withheld/unresolvable, or chainEnd cycle/truncated) and then promotes
an ancestor's claim to via, deleting the object says "Its controller recreates itâ€Ķ" with no
acknowledgement. That contradicts the documented rule that a chain Radar couldn't read gives **may
revert** for deletes.
Code

packages/k8s-ui/src/utils/gitops-write-guard.ts[R441-444]

+    if (manager.via && !directClaims.length) {
+      const source = managerRefLabel(manager.via.object)
+      const parent = `${source}, ${parentManagedBy(manager, names)}`
+      return {
Evidence
The Go side records an unfollowed stop and then promotes a Between claim to Via while keeping
chainEnd/stop (pkg/ownership/manager.go ~203-230). The UI's chainUnread correctly treats that
combination as unread (gitops-write-guard.ts 581-585). For metadata/spec it is checked at lines
437-439, inside the metadata || spec guard. For deletes it is only checked at line 476, after the
via-info return at lines 441-452 has already returned. docs/gitops.md line 319 says 'chain Radar
couldn't read | may revert | same'.

packages/k8s-ui/src/utils/gitops-write-guard.ts[433-452]
packages/k8s-ui/src/utils/gitops-write-guard.ts[474-476]
pkg/ownership/manager.go[199-232]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In classifyWrite, a delete of an object whose manager answer is `via` an ancestor, where the controller chain stopped at an unreadable owner, returns informational before the delete `chainUnread` check runs.
## Fix Focus Areas
- packages/k8s-ui/src/utils/gitops-write-guard.ts[433-452]
- packages/k8s-ui/src/utils/gitops-write-guard.ts[474-476]
## Recommended Fix
Move the `chainUnread(manager)` check out of the `metadata || spec` condition so it also covers `delete`, and run it before the `manager.via && !directClaims.length` informational return. Keep the existing operator-owned delete rule if that is intended. Add a test: a delete with via plus stop {state:'unknown'} and chainEnd 'unfollowed' should give may-revert.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread packages/k8s-ui/src/utils/gitops-write-guard.ts
Comment thread packages/k8s-ui/src/utils/gitops-write-guard.ts
Comment thread packages/k8s-ui/src/utils/gitops-write-guard.ts
Comment thread web/src/hooks/useGitOpsWriteGuard.ts Outdated
Comment thread packages/k8s-ui/src/utils/gitops-write-guard.ts
â€Ķr's checks

- A positive answer whose lead is a manager kind this UI doesn't map
  (from a newer Radar) now asks for an acknowledgement, as a claim does.
- A delete through a fallback via whose walk couldn't read its chain
  keeps the warning, unless a controller recreates the object.
- When the live answer names a different manager than the cached one,
  the cached manager's verification and status no longer carry over to
  the dialog's link.
@nadaverell
nadaverell merged commit 9467c5e into main Oct 26, 2026
11 checks passed
@nadaverell
nadaverell deleted the ownership/manager-chip-guard-main branch October 26, 2026 16:55
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