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:
AIOSAI
2026-04-22 22:55:09 -07:00
co-authored by @spawn
parent 515f877c42
commit 744ca3ab46
4 changed files with 123 additions and 20 deletions
@@ -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)
+8
View File
@@ -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