feat(system): feat(spawn): add owner field to passport — first agent is project owner (DPLAN-0143)
Co-Authored-By: @spawn <spawn@aipass>
This commit is contained in:
@@ -98,6 +98,14 @@ def grant_passport(
|
||||
# Rename any placeholder paths
|
||||
_ = rename_placeholder_paths(target, folder_name)
|
||||
|
||||
# Set owner field — first agent in the project is the owner
|
||||
passport_path = target / ".trinity" / "passport.json"
|
||||
if passport_path.exists():
|
||||
passport_data = json_handler.read_json(passport_path)
|
||||
if passport_data:
|
||||
passport_data.setdefault("citizenship", {})["owner"] = citizen_number == 1
|
||||
json_handler.write_json(passport_path, passport_data)
|
||||
|
||||
# Regenerate template registry
|
||||
regenerate_template_registry(target)
|
||||
|
||||
|
||||
@@ -217,6 +217,14 @@ def _spawn_agent(
|
||||
# Step 2: Rename any {{BRANCH}} dirs/files that weren't caught by path replacement
|
||||
renamed = rename_placeholder_paths(target, folder_name)
|
||||
|
||||
# Step 2b: Set owner field — first agent in the project is the owner
|
||||
passport_path = target / ".trinity" / "passport.json"
|
||||
if passport_path.exists():
|
||||
passport_data = json_handler.read_json(passport_path)
|
||||
if passport_data:
|
||||
passport_data.setdefault("citizenship", {})["owner"] = citizen_number == 1
|
||||
json_handler.write_json(passport_path, passport_data)
|
||||
|
||||
# Step 3: Regenerate .template_registry.json with fresh hashes
|
||||
regenerate_template_registry(target)
|
||||
|
||||
|
||||
@@ -137,6 +137,12 @@
|
||||
"content_hash": "2e4f4a0c1b47",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f022": {
|
||||
"path": "apps/handlers/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "dbfc0e044461",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f044": {
|
||||
"path": "apps/integrations/README.md",
|
||||
"name": "README.md",
|
||||
@@ -149,7 +155,7 @@
|
||||
"content_hash": "a4cf0a8e3b4f",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f045": {
|
||||
"f015": {
|
||||
"path": "apps/modules/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc",
|
||||
@@ -161,6 +167,12 @@
|
||||
"content_hash": "d1e4e2b98c38",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f027": {
|
||||
"path": "apps/{{BRANCH}}.py",
|
||||
"name": "{{BRANCH}}.py",
|
||||
"content_hash": "39db2c4f8160",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"f028": {
|
||||
"path": "artifacts/README.md",
|
||||
"name": "README.md",
|
||||
@@ -227,6 +239,12 @@
|
||||
"content_hash": "881f06bb6574",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f039": {
|
||||
"path": "tests/conftest.py",
|
||||
"name": "conftest.py",
|
||||
"content_hash": "97f220799d19",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f040": {
|
||||
"path": "tools/README.md",
|
||||
"name": "README.md",
|
||||
@@ -245,25 +263,7 @@
|
||||
"content_hash": "28e9ae373563",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f022": {
|
||||
"path": "apps/handlers/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "dbfc0e044461",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f027": {
|
||||
"path": "apps/{{BRANCH}}.py",
|
||||
"name": "{{BRANCH}}.py",
|
||||
"content_hash": "39db2c4f8160",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"f039": {
|
||||
"path": "tests/conftest.py",
|
||||
"name": "conftest.py",
|
||||
"content_hash": "97f220799d19",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f015": {
|
||||
"f026": {
|
||||
"path": "apps/plugins/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc",
|
||||
|
||||
@@ -13,6 +13,8 @@ class-aware update, and backward compatibility.
|
||||
"""
|
||||
|
||||
import json
|
||||
import unittest.mock
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -444,3 +446,88 @@ class TestMultiAgentCoexistence:
|
||||
passport = json.loads((tmp_path / name / ".trinity" / "passport.json").read_text())
|
||||
assert passport["branch_info"]["branch_name"] == name.upper()
|
||||
assert passport["identity"]["citizen_class"] == "builder"
|
||||
|
||||
|
||||
class TestPassportOwnerField:
|
||||
"""Tests verifying the owner field in passport.json."""
|
||||
|
||||
def test_first_agent_is_owner(self, tmp_path):
|
||||
"""First agent created in a project gets owner: true."""
|
||||
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))
|
||||
|
||||
passport = json.loads((tmp_path / "first" / ".trinity" / "passport.json").read_text())
|
||||
assert passport["citizenship"]["owner"] is True
|
||||
|
||||
def test_second_agent_not_owner(self, tmp_path):
|
||||
"""Second agent created in a project gets owner: false."""
|
||||
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))
|
||||
|
||||
p1 = json.loads((tmp_path / "first" / ".trinity" / "passport.json").read_text())
|
||||
p2 = json.loads((tmp_path / "second" / ".trinity" / "passport.json").read_text())
|
||||
assert p1["citizenship"]["owner"] is True
|
||||
assert p2["citizenship"]["owner"] is False
|
||||
|
||||
def test_third_agent_not_owner(self, tmp_path):
|
||||
"""Third agent also gets owner: false."""
|
||||
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":[]}')
|
||||
|
||||
for name in ["alpha", "beta", "gamma"]:
|
||||
_spawn_agent(str(tmp_path / name), registry_path=str(reg))
|
||||
|
||||
passports = {}
|
||||
for name in ["alpha", "beta", "gamma"]:
|
||||
passports[name] = json.loads((tmp_path / name / ".trinity" / "passport.json").read_text())
|
||||
|
||||
assert passports["alpha"]["citizenship"]["owner"] is True
|
||||
assert passports["beta"]["citizenship"]["owner"] is False
|
||||
assert passports["gamma"]["citizenship"]["owner"] is False
|
||||
|
||||
def test_birthright_first_agent_is_owner(self, tmp_path):
|
||||
"""First birthright agent gets owner: true."""
|
||||
from aipass.spawn.apps.handlers.passport_ops import grant_passport
|
||||
|
||||
reg = tmp_path / "TEST_REGISTRY.json"
|
||||
reg.write_text('{"metadata":{"version":"1.0.0","total_branches":0},"branches":[]}')
|
||||
|
||||
target = tmp_path / "citizen"
|
||||
target.mkdir()
|
||||
with unittest.mock.patch("aipass.spawn.apps.handlers.passport_ops.find_registry", return_value=reg):
|
||||
grant_passport(str(target))
|
||||
|
||||
passport = json.loads((target / ".trinity" / "passport.json").read_text())
|
||||
assert passport["citizenship"]["owner"] is True
|
||||
|
||||
def test_birthright_second_agent_not_owner(self, tmp_path):
|
||||
"""Second birthright agent gets owner: false."""
|
||||
from aipass.spawn.apps.handlers.passport_ops import grant_passport
|
||||
|
||||
reg = tmp_path / "TEST_REGISTRY.json"
|
||||
reg.write_text('{"metadata":{"version":"1.0.0","total_branches":0},"branches":[]}')
|
||||
|
||||
first = tmp_path / "first"
|
||||
first.mkdir()
|
||||
second = tmp_path / "second"
|
||||
second.mkdir()
|
||||
|
||||
with unittest.mock.patch("aipass.spawn.apps.handlers.passport_ops.find_registry", return_value=reg):
|
||||
grant_passport(str(first))
|
||||
grant_passport(str(second))
|
||||
|
||||
p1 = json.loads((first / ".trinity" / "passport.json").read_text())
|
||||
p2 = json.loads((second / ".trinity" / "passport.json").read_text())
|
||||
assert p1["citizenship"]["owner"] is True
|
||||
assert p2["citizenship"]["owner"] is False
|
||||
|
||||
Reference in New Issue
Block a user