feat(system): chore: gitignore HERALD.md and remove from README
Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
# Virtual environment
|
||||
.venv/
|
||||
|
||||
# Herald (session history — parked)
|
||||
HERALD.md
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](HERALD.md)
|
||||
[](#project-status)
|
||||
[](pyproject.toml)
|
||||
[](LICENSE)
|
||||
[](https://pypi.org/project/aipass/)
|
||||
@@ -232,8 +232,6 @@ setup.sh auto-detects which CLIs are installed and configures hooks for each.
|
||||
|
||||
Each agent documents its own operational status in its branch README — what works, what doesn't, and why.
|
||||
|
||||
For detailed session history, see [HERALD.md](HERALD.md).
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -34,6 +34,7 @@ from aipass.spawn.apps.handlers.registry import (
|
||||
find_registry,
|
||||
add_to_registry,
|
||||
get_next_citizen_number,
|
||||
ensure_project_has_owner,
|
||||
)
|
||||
from aipass.spawn.apps.handlers.class_registry import get_template_dir
|
||||
from aipass.spawn.apps.handlers.json import json_handler
|
||||
@@ -124,6 +125,9 @@ def grant_passport(
|
||||
purpose or "Birthright citizen - purpose TBD",
|
||||
)
|
||||
|
||||
# Ensure at least one agent in the project is the owner
|
||||
ensure_project_has_owner(reg_path)
|
||||
|
||||
# Validate
|
||||
issues = validate_no_placeholders(target)
|
||||
|
||||
|
||||
@@ -265,3 +265,34 @@ def fix_passport_registry_id(branch_dir: Path, registry_path: Path) -> bool:
|
||||
except Exception as e:
|
||||
logger.warning("[registry] Failed to fix registry_id for %s: %s", branch_dir.name, e)
|
||||
return False
|
||||
|
||||
|
||||
def ensure_project_has_owner(registry_path):
|
||||
"""If no agent in the project has owner:true, assign it to the earliest-created agent."""
|
||||
registry_path = Path(registry_path)
|
||||
reg_data = load_registry(registry_path)
|
||||
branches = _branches_as_list(reg_data.get("branches", []))
|
||||
if not branches:
|
||||
return False
|
||||
|
||||
registry_root = registry_path.parent
|
||||
for branch in branches:
|
||||
branch_path = registry_root / branch.get("path", "")
|
||||
passport_path = branch_path / ".trinity" / "passport.json"
|
||||
if passport_path.exists():
|
||||
passport = json_handler.read_json(passport_path)
|
||||
if passport and passport.get("citizenship", {}).get("owner") is True:
|
||||
return False
|
||||
|
||||
by_created = sorted(branches, key=lambda b: b.get("created", "9999-99-99"))
|
||||
for branch in by_created:
|
||||
branch_path = registry_root / branch.get("path", "")
|
||||
passport_path = branch_path / ".trinity" / "passport.json"
|
||||
if passport_path.exists():
|
||||
passport = json_handler.read_json(passport_path)
|
||||
if passport:
|
||||
passport.setdefault("citizenship", {})["owner"] = True
|
||||
json_handler.write_json(passport_path, passport)
|
||||
logger.info("[registry] Retroactively set owner=true on %s", branch.get("name", "?"))
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -46,6 +46,7 @@ from aipass.spawn.apps.handlers.registry import (
|
||||
add_to_registry,
|
||||
get_next_citizen_number,
|
||||
fix_passport_registry_id,
|
||||
ensure_project_has_owner,
|
||||
)
|
||||
from aipass.spawn.apps.handlers.class_registry import (
|
||||
get_template_dir as _get_template_dir,
|
||||
@@ -250,7 +251,10 @@ def _spawn_agent(
|
||||
purpose or "New agent - purpose TBD",
|
||||
)
|
||||
|
||||
# Step 5: Validate no unreplaced placeholders
|
||||
# Step 5: Ensure at least one agent in the project is the owner
|
||||
ensure_project_has_owner(reg_path)
|
||||
|
||||
# Step 6: Validate no unreplaced placeholders
|
||||
issues = validate_no_placeholders(target)
|
||||
|
||||
json_handler.log_operation("branch_created", data={"branch": branch_upper})
|
||||
|
||||
@@ -531,3 +531,78 @@ class TestPassportOwnerField:
|
||||
p2 = json.loads((second / ".trinity" / "passport.json").read_text())
|
||||
assert p1["citizenship"]["owner"] is True
|
||||
assert p2["citizenship"]["owner"] is False
|
||||
|
||||
|
||||
class TestRetroactiveOwner:
|
||||
"""Tests for retroactive owner assignment on legacy projects."""
|
||||
|
||||
def test_retroactive_owner_on_legacy_agents(self, tmp_path):
|
||||
"""Creating a new agent in a project where no agent has owner sets the first one."""
|
||||
from aipass.spawn.apps.modules.core import _spawn_agent
|
||||
|
||||
reg = tmp_path / "TEST_REGISTRY.json"
|
||||
reg.write_text('{"metadata":{"version":"1.0.0","total_branches":0},"branches":[]}')
|
||||
|
||||
# Create two agents, then remove their owner field to simulate legacy
|
||||
_spawn_agent(str(tmp_path / "old_a"), registry_path=str(reg))
|
||||
_spawn_agent(str(tmp_path / "old_b"), registry_path=str(reg))
|
||||
|
||||
for name in ["old_a", "old_b"]:
|
||||
pp = tmp_path / name / ".trinity" / "passport.json"
|
||||
data = json.loads(pp.read_text())
|
||||
del data["citizenship"]["owner"]
|
||||
pp.write_text(json.dumps(data, indent=2))
|
||||
|
||||
# Create a third agent — should trigger retroactive fix
|
||||
_spawn_agent(str(tmp_path / "new_c"), registry_path=str(reg))
|
||||
|
||||
pa = json.loads((tmp_path / "old_a" / ".trinity" / "passport.json").read_text())
|
||||
pb = json.loads((tmp_path / "old_b" / ".trinity" / "passport.json").read_text())
|
||||
pc = json.loads((tmp_path / "new_c" / ".trinity" / "passport.json").read_text())
|
||||
assert pa["citizenship"]["owner"] is True
|
||||
assert pb["citizenship"].get("owner") is not True
|
||||
assert pc["citizenship"]["owner"] is False
|
||||
|
||||
def test_no_retroactive_if_owner_exists(self, tmp_path):
|
||||
"""If an existing agent already has owner:true, no retroactive change."""
|
||||
from aipass.spawn.apps.modules.core import _spawn_agent
|
||||
|
||||
reg = tmp_path / "TEST_REGISTRY.json"
|
||||
reg.write_text('{"metadata":{"version":"1.0.0","total_branches":0},"branches":[]}')
|
||||
|
||||
_spawn_agent(str(tmp_path / "first"), registry_path=str(reg))
|
||||
_spawn_agent(str(tmp_path / "second"), registry_path=str(reg))
|
||||
_spawn_agent(str(tmp_path / "third"), registry_path=str(reg))
|
||||
|
||||
p1 = json.loads((tmp_path / "first" / ".trinity" / "passport.json").read_text())
|
||||
p2 = json.loads((tmp_path / "second" / ".trinity" / "passport.json").read_text())
|
||||
p3 = json.loads((tmp_path / "third" / ".trinity" / "passport.json").read_text())
|
||||
assert p1["citizenship"]["owner"] is True
|
||||
assert p2["citizenship"]["owner"] is False
|
||||
assert p3["citizenship"]["owner"] is False
|
||||
|
||||
def test_ensure_project_has_owner_direct(self, tmp_path):
|
||||
"""Direct call to ensure_project_has_owner fixes a legacy project."""
|
||||
from aipass.spawn.apps.handlers.registry import ensure_project_has_owner
|
||||
from aipass.spawn.apps.modules.core import _spawn_agent
|
||||
|
||||
reg = tmp_path / "TEST_REGISTRY.json"
|
||||
reg.write_text('{"metadata":{"version":"1.0.0","total_branches":0},"branches":[]}')
|
||||
|
||||
_spawn_agent(str(tmp_path / "agent_x"), registry_path=str(reg))
|
||||
_spawn_agent(str(tmp_path / "agent_y"), registry_path=str(reg))
|
||||
|
||||
# Strip owner from both
|
||||
for name in ["agent_x", "agent_y"]:
|
||||
pp = tmp_path / name / ".trinity" / "passport.json"
|
||||
data = json.loads(pp.read_text())
|
||||
data["citizenship"].pop("owner", None)
|
||||
pp.write_text(json.dumps(data, indent=2))
|
||||
|
||||
result = ensure_project_has_owner(reg)
|
||||
assert result is True
|
||||
|
||||
px = json.loads((tmp_path / "agent_x" / ".trinity" / "passport.json").read_text())
|
||||
py = json.loads((tmp_path / "agent_y" / ".trinity" / "passport.json").read_text())
|
||||
assert px["citizenship"]["owner"] is True
|
||||
assert py["citizenship"].get("owner") is not True
|
||||
|
||||
Reference in New Issue
Block a user