Skip to content

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

Merged
nadaverell merged 28 commits into
ownership/manager-contextfrom
ownership/manager-chip-guard
Oct 26, 2026
Merged

nadaverell merged 28 commits into
ownership/manager-contextfrom
ownership/manager-chip-guard

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #2320, which is on #2319.

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.

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.
@nadaverell
nadaverell requested a review from hisco as a code owner October 26, 2026 03:36
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Use server manager answers for GitOps chips and write warnings

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Show server-confirmed managers in resource chips, including disagreements, shared ownership, and
 inherited management.
• Compute write evidence from live metadata and apply conservative warnings when ownership is
 uncertain.
• Preserve older Radar behavior and add server, classifier, hook, and chip coverage.
Diagram

graph TD
  Cache[("Ownership cache")] --> View["Manager view"] --> Answer["Manager answer"] --> Chip["Header chip"]
  Dialog["Write dialog"] --> Hook["Guard hook"] --> API["Evidence API"] --> Live["Live target"] --> View
  Answer --> Hook
  API --> Hook
Loading
High-Level Assessment

Reusing the server's manager view is preferable to extending browser-side label inference or trusting the client's owner hint: it accounts for recorded membership and caller access. Keeping the legacy path only when no answer is available preserves compatibility with older Radars.

Files changed (15) +814 / -55

Enhancement (5) +196 / -28
ownership.goAnswer management using live target metadata +38/-0

Answer management using live target metadata

• Adds a manager-answer path that overlays the fetched target onto a cache-backed ownership session. It retains cache validation, UID replacement checks, and caller access restrictions.

internal/k8s/ownership.go

gitops_write_evidence.goReturn a server-computed manager with write evidence +17/-6

Return a server-computed manager with write evidence

• Computes the caller-scoped manager answer after reading the live target and returns it in the response. Its lead selects policy evidence; client owner hints and name-based Argo resolution apply only when no answer is available.

internal/server/gitops_write_evidence.go

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

Render chips from structured manager answers

• Adds answer-based labels, tooltips, warnings, and namespace-qualified navigation while retaining the legacy owner-based chip. Native Helm chips can also display manager-answer context.

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

WorkloadView.tsxUse manager answers throughout the resource header +17/-12

Use manager answers throughout the resource header

• Prefers answer-backed GitOps or native Helm chips over previously resolved browser owners. Hides the Managed By group only when the chip fully represents its single entry.

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

gitops-owner.tsProject manager evidence into chip and guard candidates +83/-8

Project manager evidence into chip and guard candidates

• Adds shared helpers for active claims, deduplicated manager candidates, chip copy, and exact Managed By group coverage. Relationship owner resolution now prefers an available manager answer while retaining the older projection.

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

Bug fix (3) +101 / -21
gitops-write-guard.tsCompose manager certainty with existing write rules +73/-9

Compose manager certainty with existing write rules

• Uses the server answer over client ownership, warning conservatively for competing or unconfirmed managers and unreadable ancestry. Keeps status, child-creation, operator-delete, and empty-path behavior while making inherited ownership informational when the target has no active claim.

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

useGitOpsWriteGuard.tsFetch evidence for manager answers without an owner +14/-3

Fetch evidence for manager answers without an owner

• Requests write evidence for relevant manager states even when they have no lead. Passes manager context into classification and makes the response's answer override previously resolved browser ownership.

web/src/hooks/useGitOpsWriteGuard.ts

useResolvedGitOpsOwner.tsStop browser ownership inference when an answer exists +14/-9

Stop browser ownership inference when an answer exists

• Exposes the relationship manager answer and skips parent lookup and Argo namespace guessing when present. Continues direct lead status reads and preserves legacy resolution for older Radars.

web/src/hooks/useResolvedGitOpsOwner.ts

Tests (6) +511 / -2
ownership_test.goTest live manager overlay and access boundaries +75/-0

Test live manager overlay and access boundaries

• Checks that live labels win for the same UID, replaced objects and switched caches cannot yield an answer, and restricted reads remain restricted.

internal/k8s/ownership_test.go

gitops_write_evidence_test.goVerify server manager precedence over client hints +64/-0

Verify server manager precedence over client hints

• Exercises a confirmed Application and conflicting or negative answers. Verifies the server-selected owner and policy cannot be replaced by a client-supplied owner.

internal/server/gitops_write_evidence_test.go

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

Test manager-answer chip wording

• Covers confirmed, claimed, disagreeing, shared, inherited, and absent answers, including claim-check explanations. Checks plural labels, copied-claim omission, and suppression of a single manager's status on shared answers.

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

WorkloadView.relationships.test.tsxTest when the manager chip replaces the Managed By group +35/-0

Test when the manager chip replaces the Managed By group

• Checks that only an exact, direct, single-recorder answer hides the group. Covers differing managers, coverage notes, inherited answers, and native Helm presentation.

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

gitops-write-guard.test.tsTest manager-aware write verdict boundaries +155/-0

Test manager-aware write verdict boundaries

• Covers ambiguous managers, unconfirmed claims, inherited ownership, unreadable chains, and gone or replaced owners. Verifies operation-specific rules, acknowledgment requirements, policy exemptions, and legacy behavior without a manager answer.

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

useGitOpsWriteGuard.test.tsxTest evidence fetching and server-answer precedence +100/-2

Test evidence fetching and server-answer precedence

• Verifies evidence requests for ambiguous and inherited answers and adoption of a conflicting server answer. Checks that answer-backed resolution skips browser inheritance and Argo namespace guessing while still fetching a known lead's status.

web/src/hooks/useGitOpsWriteGuard.test.tsx

Documentation (1) +6 / -4
gitops.mdDocument manager-backed chips and write warnings +6/-4

Document manager-backed chips and write warnings

• Explains chip wording, group-hiding rules, inherited answers, and older-Radar fallback. Documents server-selected write policy and conservative outcomes for ambiguous or unconfirmed ownership.

docs/gitops.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 (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. New tracking can bypass write warnings ✓ Resolved
Description
useGitOpsWriteGuard disables the evidence request for a cached none or unknown manager answer
without a stop or chain end, because neither managed nor needsManagerEvidence is true. If the
live target has since gained GitOps tracking metadata, the guard never asks the endpoint for its
live manager answer and can allow the write without the warning that answer would require.
Code

web/src/hooks/useGitOpsWriteGuard.ts[R85-86]

+  const manager = ownership.manager
+  const needsManagerEvidence = Boolean(manager && (manager.via || !['none', 'unknown'].includes(manager.state) || manager.stop || manager.chainEnd))
Evidence
The new gate excludes negative answers, while the endpoint reads the target live and derives its
manager from that object. The live-overlay test demonstrates that live tracking metadata can differ
from the cached object.

web/src/hooks/useGitOpsWriteGuard.ts[82-105]
internal/server/gitops_write_evidence.go[172-200]
internal/k8s/ownership_test.go[241-267]

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 cached negative manager answer prevents the write guard from requesting live evidence, even when the target's live metadata now names a manager.
## Fix Focus Areas
- web/src/hooks/useGitOpsWriteGuard.ts[84-105]
## Recommended Fix
Allow an available manager answer, including `none` and `unknown`, to trigger the live evidence request. Keep the existing pending and error handling conservative while that request runs.

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


2. One application appears as two managers ✓ Resolved
Description
managerCandidates resolves a namespace-less claim only against recorders, not against the answer's
namespaced lead. When an Argo tracking ID and instance label name the same Application but its
status does not record the object, the chip displays both forms as separate managers and the write
guard takes its multiple-manager branch.
Code

packages/k8s-ui/src/utils/gitops-owner.ts[R88-92]

+  const claims = activeManagerClaims(evidence).map(c => {
+    const ref = c.manager
+    return !ref.namespace ? recorders.find(r => r.kind === ref.kind && r.group === ref.group && r.name === ref.name) ?? ref : ref
+  })
+  return dedupeResourceRefs([...(answer.lead ? [answer.lead] : []), ...claims, ...recorders])
Evidence
The backend explicitly tests this pair of claims as a claimed answer with one namespaced lead. The
frontend retains the namespace-less claim without a recorder, and its deduplication key includes
namespace; the chip and classifier both use that resulting candidate count.

pkg/ownership/manager_test.go[197-207]
pkg/ownership/manager.go[280-316]
packages/k8s-ui/src/utils/gitops-owner.ts[85-111]
packages/k8s-ui/src/utils/resource-refs.ts[9-23]
packages/k8s-ui/src/utils/gitops-write-guard.ts[447-452]

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 namespace-less Argo instance-label claim and a namespaced tracking-ID claim can name one Application, but the candidate list counts them twice when there is no recorder.
## Fix Focus Areas
- packages/k8s-ui/src/utils/gitops-owner.ts[85-93]
## Recommended Fix
Resolve a namespace-less claim against a matching namespaced lead or claim before deduplication, while retaining distinct claims when their manager identities do not match. Add a test using both Argo metadata forms without a recorder.

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


3. Helm ReplicaSet edits get a GitOps-style revert warning ✓ Resolved
Description
In classifyWrite, directClaims drops helm-release claims only when viaGitOps is true. For a
plain Helm ancestor, viaGitOps is false, so the Helm annotation that the Deployment controller
copies onto each ReplicaSet counts as the ReplicaSet's own claim. That claim is weak and not flagged
copied, so the guard skips the "generated from <parent>" note. Every metadata or spec edit on a
ReplicaSet of a plain Helm Deployment then requires the acknowledgement. Its reason claims the
HelmRelease's "ignore rules or sync policy cannot exempt this change", even though Helm has neither.
The Pods of the same Deployment get only the note.
Code

packages/k8s-ui/src/utils/gitops-write-guard.ts[R424-426]

+    const viaGitOps = manager.via && managerCandidates(manager).some(ref => gitOpsOwnerFromRef(ref) !== null)
+    const directClaims = activeManagerClaims(manager.direct).filter(c => !viaGitOps || c.source !== 'helm-release')
+    const names = dedupeResourceRefs([...candidates, ...(manager.via ? directClaims.map(c => c.manager) : [])]).map(managerRefLabel).join(' and ')
Evidence
The Kubernetes Deployment controller copies a Deployment's annotations, including
meta.helm.sh/release-name, onto its ReplicaSets. Claims turns that annotation into a
ClaimHelmRelease claim. Only Argo tracking IDs ever get Copied, so the claim stays active.
managerEvidence marks it Weak because the ReplicaSet has a controller, so Manager walks on to
the Deployment and sets Via. In the client, gitOpsOwnerFromRef returns null for a native
HelmRelease (empty group), so viaGitOps is false. activeManagerClaims(manager.direct) keeps the
helm claim, so directClaims.length > 0. The info branch is skipped and the branch for `manager.via
&& directClaims.length > 0` returns may-revert with the multi-manager, sync-policy wording.

pkg/gitops/claims.go[51-86]
pkg/ownership/manager.go[216-235]
packages/k8s-ui/src/utils/gitops-owner.ts[43-54]
packages/k8s-ui/src/utils/gitops-write-guard.ts[421-450]

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`, when the manager answer is inherited from a plain Helm ancestor (`viaGitOps` false), the `helm-release` claim on a ReplicaSet still counts as the object's own claim. The Deployment controller copied that annotation from the Deployment. Because the claim counts, the guard returns may-revert with GitOps wording ("ignore rules or sync policy") instead of the "generated from <parent>" note that Pods get.
## Fix Focus Areas
- packages/k8s-ui/src/utils/gitops-write-guard.ts[424-426]
## Recommended Fix
When `manager.via` is set, leave out of `directClaims` any `helm-release` claim marked `weak`, whatever the ancestor's tool (for example `.filter(c => !(manager.via && c.weak && c.source === 'helm-release'))`). Simpler still, drop every `helm-release` claim whenever `manager.via` is set. Add a classifier test: a ReplicaSet whose direct evidence has only a weak `helm-release` claim and whose `via` is a native-Helm-claimed Deployment should get the info verdict.

ⓘ 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 web/src/hooks/useGitOpsWriteGuard.ts Outdated
Comment thread packages/k8s-ui/src/utils/gitops-owner.ts
Comment thread packages/k8s-ui/src/utils/gitops-write-guard.ts Outdated
…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.
…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.
…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.
…nes Radar found

The app passed the chip's open handler only when it could fetch the
lead's object, so a disagreement, which has no lead, opened nothing.
With a manager answer the handler is passed; the chip links a manager
only when its list names the object or Radar read the manager its
metadata names, never a missing, withheld or unresolved one.
…values from newer Radars

A Job created from a CronJob (scheduled or by hand) runs once: deleting
it no longer says its controller recreates it. A manager state, claim
check or source this UI doesn't know is shown generically and read as an
unconfirmed claim, instead of throwing in the tooltip or linking. A
manager one claim read is linked even when another claim left it
unresolved.
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:'.
- 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.
@cursor

cursor Bot commented Oct 26, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

When the answer came from an ancestor above the CronJob, the note said
that ancestor could create new Jobs on its schedule.
@nadaverell
nadaverell merged commit 7583889 into ownership/manager-context Oct 26, 2026
nadaverell added a commit that referenced this pull request Oct 26, 2026
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 <Kind>" | "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 <parent>, 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.
@nadaverell
nadaverell deleted the ownership/manager-chip-guard 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