#679 spawn: is_owner() case-folds — is_owner('DEVPULSE') == is_owner('devpulse').
registry.is_owner (apps/handlers/registry.py:382) @-normalized the email but never lowercased, so a mixed-case branch name (registry names are mixed-case: DEVPULSE vs devpulse) returned False against the seated owner while the lowercase form returned True. Harmless today — the only live caller (@ai_mail dispatch_monitor._wake_sender) lowercases first — but the frozen TDPLAN-0012 contract promises a normalized email, and PART-4 owner-gating of watchdog/feedback may pass a raw branch name. Fix: lowercase BOTH sides of the comparison (passed-in email AND registry owner email), @-strip preserved. +1 case-insensitivity test. Built by @spawn, verified by devpulse: LIVE repro — every case variant of the owner (DEVPULSE/@DEVPULSE/DevPulse) resolves True, non-owners (seedgo/@SEEDGO) and empty stay False; 316 spawn tests green (+1), seedgo 100%. Rides PR#659 (issue-clearing, no main-merge). Source: #678/TDPLAN-0012 verify.
This commit is contained in:
@@ -47,6 +47,15 @@ PyPI version — not the changelog header.
|
||||
relays. Stall logic extracted into a `StallTracker` for clarity; +9 tests
|
||||
(142 green), devpulse audit 100%. (devpulse)
|
||||
|
||||
- **`is_owner()` now case-folds — `is_owner('DEVPULSE')` matches `is_owner('devpulse')`
|
||||
(issue #679).** The spawn-registry resolver (`registry.py:382`) `@`-normalized the
|
||||
email but never lowercased, so a mixed-case branch name (registry names are
|
||||
mixed-case: `DEVPULSE` vs `devpulse`) returned `False` against the seated owner.
|
||||
Harmless today (the only caller lowercases first) but the frozen TDPLAN-0012
|
||||
contract promises normalization, and PART-4 owner-gating may pass a raw name.
|
||||
Now lowercases both sides; verified live (every case variant of the owner → True,
|
||||
non-owners → False) + a case-insensitivity test (316 green). (@spawn, verified devpulse)
|
||||
|
||||
- **`aipass install` no longer hard-fails (exit 2, silently) when it can't create
|
||||
global symlinks (issue #660 follow-up).** `setup.sh` runs under
|
||||
`set -euo pipefail`; the #660 `safe_symlink` refactor returns `2` on `ln`
|
||||
|
||||
@@ -379,10 +379,10 @@ def is_owner(email, start_path=None):
|
||||
"""
|
||||
if not email:
|
||||
return False
|
||||
normalized = email if email.startswith("@") else f"@{email}"
|
||||
normalized = (email if email.startswith("@") else f"@{email}").lower()
|
||||
owner = get_owner(start_path=start_path)
|
||||
if owner is None:
|
||||
return False
|
||||
owner_email = owner.get("email", "")
|
||||
owner_normalized = owner_email if owner_email.startswith("@") else f"@{owner_email}"
|
||||
owner_normalized = (owner_email if owner_email.startswith("@") else f"@{owner_email}").lower()
|
||||
return normalized == owner_normalized
|
||||
|
||||
@@ -154,6 +154,15 @@ class TestIsOwner:
|
||||
with patch("aipass.spawn.apps.handlers.registry.find_registry", return_value=registry_with_owner):
|
||||
assert is_owner("devpulse", start_path=tmp_path) is True
|
||||
|
||||
def test_case_insensitive(self, registry_with_owner, tmp_path):
|
||||
from aipass.spawn.apps.handlers.registry import is_owner
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.registry.find_registry", return_value=registry_with_owner):
|
||||
assert is_owner("DEVPULSE", start_path=tmp_path) is True
|
||||
assert is_owner("@DEVPULSE", start_path=tmp_path) is True
|
||||
assert is_owner("DevPulse", start_path=tmp_path) is True
|
||||
assert is_owner("ALPHA", start_path=tmp_path) is False
|
||||
|
||||
def test_false_for_non_owner(self, registry_with_owner, tmp_path):
|
||||
from aipass.spawn.apps.handlers.registry import is_owner
|
||||
|
||||
|
||||
Reference in New Issue
Block a user