Skip to content

fix: identify a policy's plans by owner UID, not by a truncated label - #151

Closed
hardbyte wants to merge 4 commits into
claude/stack-3-e2e-hostile-identifiersfrom
claude/stack-4-uid-ownership
Closed

fix: identify a policy's plans by owner UID, not by a truncated label#151
hardbyte wants to merge 4 commits into
claude/stack-3-e2e-hostile-identifiersfrom
claude/stack-4-uid-ownership

Conversation

@hardbyte

@hardbyte hardbyte commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Stack 4 of 4 — base is #149. Review #147, #148 and #149 first.

Closes the collision CodeRabbit raised on #147. #148 prevents new lossy labels; this makes the lookups exact regardless, so a policy that predates the CEL rule is covered too.

The bug

pgroles.io/policy was the only link between a policy and its plans and plan-SQL ConfigMaps, but the label truncates at 63 characters while a policy name may be 253. Two policies sharing a 63-character prefix selected each other's objects, and that selector drives deletion in cleanup_old_plans and cleanup_orphan_sql_configmaps, plus approval and deduplication in the three lookup paths. LabelValue::sanitize's own docs in #147 warn against this exact use.

Why UID, and why all six paths at once

Filter by controller-owner UID. The label still narrows server-side so the list stays cheap; the UID decides. Owner references are already written on both plans and ConfigMaps, so no new label and no migration.

The ConfigMap path is why this can't be plan-side only, and it's worth being explicit because the obvious partial fix is actively harmful: is_orphan_sql_configmap deletes a ConfigMap whose plan label is absent from the known set. Exact-filtering the plans while leaving the ConfigMap list truncation-matched would make a colliding policy's ConfigMap look more like an orphan — converting a lookup bug into a deletion bug. So I did not take the suggested spec.policy_ref.name post-filter as written.

UID over spec.policyRef.name for a second reason: it's exact per object lifetime. A replacement policy with the same name is a different object and must not inherit the deleted one's plans. An absent UID owns nothing rather than acting as a wildcard.

Same fix in main.rs, which compared the truncated label against metadata.name and so silently never matched above 63 characters — plan status changes could not wake the policy reconciler at all. That also retires the last use of the label as an identity.

Tests

Five unit tests, each pinning a way this could regress: a genuine 63-character-prefix collision (asserting first that the two labels really are identical, so the test can't rot into vacuity), a missing owner reference, a non-controller owner reference, an empty UID not acting as a wildcard, a recreated same-name policy, and the ConfigMap path specifically.

Verification

cargo fmt --check, clippy -D warnings, full workspace (274 operator unit tests), and check-crd-drift.sh all pass.

https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved policy and plan ownership tracking, preventing resources from being incorrectly adopted, deduplicated, superseded, or deleted.
    • Added validation for stacked pull request metadata, including position and size ranges.
    • Prevented name collisions between policies from affecting plan creation and reconciliation.
  • Documentation

    • Documented the 63-character policy name limit and upgrade behavior for existing longer names.
    • Documented orphaning behavior for plans and SQL ConfigMaps when policies are deleted with cascade orphaning.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 74406ed5-c0bd-4fa4-b7c4-cf0d0e3de23d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The operator now identifies policies by controller owner UID and filters plans and ConfigMaps by that ownership. Tests cover collisions and lifecycle cases. Documentation records policy-name and orphaning limits. CI validates stacked pull request metadata before running the heavy suite.

Changes

Operator ownership isolation

Layer / File(s) Summary
Controller UID ownership identity
crates/pgroles-operator/src/main.rs, crates/pgroles-operator/src/plan.rs
Policy lookup and ownership checks now use controller owner-reference UIDs. Name collisions owned by another policy return a storage error.
Owned plan and ConfigMap filtering
crates/pgroles-operator/src/plan.rs
Plan lookup, deduplication, supersession, cleanup, and ConfigMap orphan handling now use policy ownership filters.
Ownership validation and limitations
crates/pgroles-operator/src/plan.rs, docs/src/pages/docs/limitations.md
Tests cover ownership collisions, missing references, recreated policies, and ConfigMap isolation. Documentation records the 63-character name limit and orphaning behavior.

Stack metadata validation

Layer / File(s) Summary
Fail-safe stacked pull request gate
.github/workflows/ci.yml
The workflow rejects partial or out-of-range stack metadata before the expensive CI suite runs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Plan
  participant PostgresPolicy API
  participant Reconciliation
  Plan->>PostgresPolicy API: provide controller owner UID
  PostgresPolicy API-->>Reconciliation: return matching PostgresPolicy
  Reconciliation->>Plan: create reconciliation reference
Loading

Possibly related PRs

Poem

A rabbit checks each owner UID,
While plans stay with the proper tree.
Labels guide, but cannot prove,
Cleanup guards each owned groove.
CI hops past metadata fright—
Safe stacks run heavy tests right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: identifying policy plans by owner UID instead of truncated labels.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Auto-review was skipped here because this is a stacked PR whose base is claude/stack-3-e2e-hostile-identifiers rather than the default branch. Requesting a one-off review — this layer changes the plan lookup and cleanup paths that perform deletion, so it's the one most worth a look.

Note for reviewers: this PR currently has no CI. It was created by setting base via the API, which does not join GitHub's native stack, so the workflow's pull_request: branches: [main] filter excludes it and stack-aware triggering doesn't apply. It needs adding to the stack in the UI.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@hardbyte I will review pull request #151. I will check the lookup and cleanup paths, including deletion behavior and the stacked changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
crates/pgroles-operator/src/plan.rs (1)

348-374: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Leaked SQL ConfigMap on cross-policy name collision.

The sql-content ConfigMap is created at line 279, before the ownership check at line 358. When is_owned_by_policy(&existing, policy) is false, the function returns ReconcileError::PlanSqlStorage at line 359 without calling delete_configmap_best_effort. The generic error branch at lines 368-372 does clean up the ConfigMap, but this branch does not.

The orphan reaper (cleanup_orphan_sql_configmaps) eventually removes this ConfigMap once ORPHAN_GRACE_SECS passes, since it is owned by the attempting policy's UID. This limits the impact to a transient orphan rather than a permanent leak, but the same cleanup call used in the other error branch should apply here too.

🧹 Proposed fix to clean up the ConfigMap on ownership mismatch
                 if !is_owned_by_policy(&existing, policy) {
+                    if let Some(configmap_name) = sql_configmap_name.as_deref() {
+                        delete_configmap_best_effort(client, &namespace, configmap_name).await;
+                    }
                     return Err(ReconcileError::PlanSqlStorage(format!(
                         "plan {plan_name} already exists and is owned by another policy"
                     )));
                 }
🤖 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 `@crates/pgroles-operator/src/plan.rs` around lines 348 - 374, Update the
ownership-mismatch branch in the plan creation match, before returning
ReconcileError::PlanSqlStorage, to call delete_configmap_best_effort with the
existing client, namespace, and sql_configmap_name. Preserve the current
ownership validation and error message, and apply cleanup only when
is_owned_by_policy returns false.
🧹 Nitpick comments (1)
crates/pgroles-operator/src/plan.rs (1)

1510-1637: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider a test for the create_or_update_plan collision-with-foreign-owner path.

The new tests cover is_owned_by_policy directly and its callers indirectly through unit-level assertions, but none exercise the create_or_update_plan 409-collision branch (lines 353-362) where an existing plan is owned by a different policy. That branch is the one mutation site the comment at line 354-357 calls out as not covered by the general owner-UID discipline, so it is the highest-value path to test end-to-end.

🤖 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 `@crates/pgroles-operator/src/plan.rs` around lines 1510 - 1637, Extend the
ownership tests around create_or_update_plan to cover a 409 conflict when an
existing plan is owned by a different policy, using distinct owner UIDs and
colliding policy labels. Assert that the collision branch preserves the
foreign-owned plan and does not update or delete it, while retaining the
existing is_owned_by_policy assertions.
🤖 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 @.github/workflows/ci.yml:
- Around line 49-60: Update the stack metadata validation in the workflow before
the range comparison to reject any STACK_POSITION or STACK_SIZE value that is
not a positive decimal integer. Preserve the existing partial-population and
1..STACK_SIZE range checks, and exit 1 with an appropriate error before numeric
comparisons when either value is invalid.

---

Outside diff comments:
In `@crates/pgroles-operator/src/plan.rs`:
- Around line 348-374: Update the ownership-mismatch branch in the plan creation
match, before returning ReconcileError::PlanSqlStorage, to call
delete_configmap_best_effort with the existing client, namespace, and
sql_configmap_name. Preserve the current ownership validation and error message,
and apply cleanup only when is_owned_by_policy returns false.

---

Nitpick comments:
In `@crates/pgroles-operator/src/plan.rs`:
- Around line 1510-1637: Extend the ownership tests around create_or_update_plan
to cover a 409 conflict when an existing plan is owned by a different policy,
using distinct owner UIDs and colliding policy labels. Assert that the collision
branch preserves the foreign-owned plan and does not update or delete it, while
retaining the existing is_owned_by_policy assertions.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d65faca-dae7-44ea-a606-f632fdb20495

📥 Commits

Reviewing files that changed from the base of the PR and between 34db89f and bec34c3.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • crates/pgroles-operator/src/main.rs
  • crates/pgroles-operator/src/plan.rs
  • docs/src/pages/docs/limitations.md

Comment thread .github/workflows/ci.yml
hardbyte added a commit that referenced this pull request Aug 1, 2026
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
@hardbyte
hardbyte force-pushed the claude/stack-4-uid-ownership branch from 7ad4d42 to 29c8ac3 Compare August 1, 2026 00:42
hardbyte added a commit that referenced this pull request Aug 1, 2026
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
hardbyte added a commit that referenced this pull request Aug 1, 2026
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
@hardbyte
hardbyte force-pushed the claude/stack-4-uid-ownership branch from 29c8ac3 to b1ef718 Compare August 1, 2026 01:15
hardbyte added a commit that referenced this pull request Aug 2, 2026
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
@hardbyte
hardbyte force-pushed the claude/stack-4-uid-ownership branch from b1ef718 to eb7d264 Compare August 2, 2026 04:49
hardbyte added a commit that referenced this pull request Aug 2, 2026
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
@hardbyte
hardbyte force-pushed the claude/stack-4-uid-ownership branch from eb7d264 to 27682d2 Compare August 2, 2026 04:58

hardbyte commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Both E2E failures share one cause, and it is the stack topology rather than the new scenarios.

E2E: Operator Scenarios and E2E: Plan Lifecycle both fail with:

The PostgresPolicy "aaa…eee" is invalid: metadata.name must be at most 63 characters

That CEL rule was removed along with #148. Neither branch head still has it — k8s/crd.yaml at both eb7d264 and 27682d2 contains zero x-kubernetes-validations entries.

The reason it is still enforced is that a pull_request build tests the merge ref, not the head, and the stack chains those merges:

#149 merge:  Merge 738b17e into 257c666      ← 257c666 is the #148 branch
#151 merge:  Merge 27682d2 into a00a249      ← a00a249 is #149's merge commit

Both merged trees contain "rule": "self.metadata.name.size() <= 63" and the KubeSchema derive. Because #148 is still a member of the stack, its CEL rule propagates up into everything CI actually runs, the kind cluster installs that CRD, and the new fixtures are rejected at admission — the 253-byte policy in Operator Scenarios, and the two 67-byte colliding policies in Plan Lifecycle.

No code change here can fix it. The only edits that would go green are reinstating the rule or shrinking the fixtures under 63 bytes, and the latter would delete precisely the boundary coverage these scenarios exist to provide: the 253-byte name places a dot on generate_plan_name's 215-byte cut, and the colliding pair must exceed 63 bytes to produce the identical label the UID filter has to separate.

This clears once #148 is removed from the stack, which has to happen in the stack UI — PATCH /pulls/149 to re-parent onto #147 is refused with 422: Cannot change the base branch because the pull request is part of a stack. After that the merge refs recompute without the rule and both jobs should pass.

Everything else is green at the tip: cargo fmt --check, clippy -D warnings, the full workspace, and check-crd-drift.sh.


Generated by Claude Code

`pgroles.io/policy` was the only link between a policy and its plans and
plan-SQL ConfigMaps, but the label value is truncated at 63 characters
while a policy name may be 253. Two policies sharing a 63-character prefix
therefore selected each other's objects, and that selector result drives
deletion in `cleanup_old_plans` and `cleanup_orphan_sql_configmaps` as
well as approval and deduplication in the three lookup paths. The module
documentation for `LabelValue::sanitize` warns against exactly this use.

Filter every one of those six paths by controller-owner UID. The label
still narrows server-side, which keeps the list cheap; the UID decides.
Owner references are already written on both plans and ConfigMaps, so
this needs no new label and no migration for existing objects.

The ConfigMap path is why this has to be done as one change rather than
plan-side only. `is_orphan_sql_configmap` treats a ConfigMap whose plan
label is absent from the known set as an orphan and deletes it, so
exact-filtering the plans while leaving the ConfigMap list
truncation-matched would have made a colliding policy's ConfigMap look
*more* like an orphan — turning a lookup bug into a deletion bug.

Apply the same fix to the plan-to-policy watch mapping in `main.rs`, which
compared the truncated label against `metadata.name` and so silently never
matched for any name over 63 characters, leaving plan status changes
unable to wake the policy reconciler.

UID rather than `spec.policyRef.name` because it is exact per object
*lifetime*: a replacement policy with the same name is a different object
and must not inherit the deleted one's plans. An absent UID is treated as
owning nothing rather than as a wildcard.

Reported by CodeRabbit review on #147.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
Self-review follow-ups on the identifier stack.

The 409 create-conflict path adopted whatever plan held the colliding name
and patched its status without an ownership check, making it the one
mutation site the owner-UID discipline did not cover. Reaching it across
policies needs two names sharing a 215-byte truncated prefix colliding in
the same second, so this is defence in depth rather than a live bug, but
the check belongs there for the same reason as the other six.

Harden the stack gate. Stacked pull requests are in public preview, and
if `position`/`size` semantics ever changed then `position == size` would
be silently false for the real top entry — the stack would merge with the
expensive suite never having run. Assert the documented 1-based range and
fail loudly instead of skipping wrongly.

Document two migration hazards that CI cannot catch, since the E2E kind
cluster runs a Kubernetes version where neither applies:

- Before Kubernetes 1.30 there is no CRD validation ratcheting, so an
  existing policy whose name exceeds the new 63-character limit has every
  write rejected, including the operator's own status updates. It stops
  making progress with nothing in its status to explain why.
- `--cascade=orphan` strips the owner references that both re-adoption and
  garbage collection now depend on, so orphaned plans and plan-SQL
  ConfigMaps persist until deleted by hand.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
The unit tests cover `is_owned_by_policy` directly, but nothing exercised
two genuinely colliding policies against a real API server — which is the
scenario the whole layer exists for.

Two policies sharing a 63-byte prefix produce an identical
`pgroles.io/policy` label, and the scenario asserts that collision holds
before relying on it, so the fixture cannot silently stop colliding. It
then checks the three things the label previously decided and the owner
UID now decides: each plan is owned by its own policy's UID, approving one
policy's plan leaves the other's Pending with its role uncreated, and
deleting one policy garbage-collects only its own plan while the other
stays discoverable and still applies cleanly afterwards.

That last step is the important one. `is_orphan_sql_configmap` deletes
objects whose plan label is absent from the known set, so a half-applied
ownership filter would have turned this collision into cross-policy
deletion rather than merely a confused lookup.

Also drop the policy-name limit from the limitations page, which described
a CRD rule that no longer exists, keeping only the `--cascade=orphan`
note, which is a genuine consequence of matching by owner UID.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4
@hardbyte
hardbyte force-pushed the claude/stack-4-uid-ownership branch from 27682d2 to 7a3b3a7 Compare August 2, 2026 05:39
hardbyte added a commit that referenced this pull request Aug 2, 2026
…match

The stack-metadata guard could be bypassed by the thing it guards against.
`[ non-numeric -lt 1 ]` writes "integer expression expected" to stderr and
evaluates *false*, and a failing command inside an `if` condition does not
trip `bash -e`, so the step continued and the expensive suite could still
be skipped — exactly the silent-skip the guard was added to prevent.
Reproduced it, then validated both values as non-negative integers before
comparing, forcing decimal with `10#` so a zero-padded value is never read
as octal. Verified against non-numeric, negative, exponent-form,
zero-padded, partial, in-range and out-of-range inputs; the unstacked
empty case still fails open as intended.

The ownership-mismatch arm of the plan-creation conflict path returned
without deleting the SQL ConfigMap it had already created, unlike the
sibling error arm. The orphan reaper would collect it, since it carries
this policy's UID, but the leak is pointless. Clean it up.

Reported by CodeRabbit review on #151.

Claude-Session: https://claude.ai/code/session_01KKdSeCxJEVcrH4MPLgwUG4

hardbyte commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #152, which carries these four commits unchanged along with the rest of the stack.

The UID-ownership change was never the problem — CI here was building refs/pull/151/merge, which chains through the frozen refs/pull/149/merge and so contained the dropped CEL rule regardless of what any branch head held. #152 is off main, and its merge ref resolves correctly.


Generated by Claude Code

@hardbyte hardbyte closed this Aug 2, 2026
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