fix(system): S59-S60 cross-branch fixes — stale refs, queue spam, close bug, template (#141)

- Daemon: 14 stale @dev_central→@devpulse and @seed→@seedgo refs fixed across 12 files
- Prax: event_queue spam eliminated (144k logs/4hrs), cortex→spawn refs, bypass added
- Flow: close_ops hardened with shutil.move + auto-healing for orphaned plans
- Spawn: builder template handler guard populated, conftest registry protection added
- Seedgo: handler guard pytest exception, test_json_handler fix
- Commons: extract_mentions dedup bug (SELECT DISTINCT LOWER)
- 7 branches: @seed→@seedgo in handlers/__init__.py
- Memory: @seed→@seedgo handler ref

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIPass
2026-03-28 21:58:29 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 6f2333ec67
commit ae459853f0
31 changed files with 339 additions and 153 deletions
+1
View File
@@ -97,3 +97,4 @@ src/aipass/memory/config/fragmented_memory_state.json
.vscode
src/aipass/memory/config/memory_bank.config.json
src/aipass/spawn/templates/builder/.spawn/.template_registry.json
branch_audits _only
+1 -1
View File
@@ -102,7 +102,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
+1 -1
View File
@@ -104,7 +104,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
+10
View File
@@ -0,0 +1,10 @@
"""Pytest configuration for backup branch.
Excludes backup data directories from test collection to prevent
pytest from recursing into system_snapshot and versioned_backup
which contain full copies of the AIPass repo tree.
"""
collect_ignore_glob = [
"backups/*",
]
+1 -1
View File
@@ -123,7 +123,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
+1 -1
View File
@@ -104,7 +104,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
@@ -182,7 +182,7 @@ def _dispatch_action(
message=action.get("prompt", name),
from_branch='@daemon',
auto_execute=True,
reply_to='@dev_central',
reply_to='@devpulse',
)
if email_sent:
assert mark_reminder_completed is not None
@@ -112,7 +112,7 @@ def create_action(
name: Human-readable action name (e.g., "daily_audit")
action_type: "plugin" | "schedule" | "reminder"
schedule_type: "daily" | "hourly" | "interval" | "once"
target_branch: Target branch email (e.g., "@seed")
target_branch: Target branch email (e.g., "@seedgo")
prompt: What the dispatched agent should do
time: For daily: "HH:MM", for hourly: "MM"
interval_minutes: For interval schedule type
@@ -179,7 +179,7 @@ def create_task(
Args:
task: Brief description of the task/follow-up
due_date: When to trigger (supports "7d", "1w", "YYYY-MM-DD")
recipient: Target branch (e.g., "@dev_central")
recipient: Target branch (e.g., "@devpulse")
message: Message to deliver when due
Returns:
@@ -501,7 +501,7 @@ def process_due_tasks_batch(
message=email_body,
from_branch='@daemon',
auto_execute=True,
reply_to='@dev_central',
reply_to='@devpulse',
)
if email_sent:
@@ -574,7 +574,7 @@ if __name__ == "__main__":
test_task = create_task(
task="Test backup health check",
due_date="7d",
recipient="@dev_central",
recipient="@devpulse",
message="Please verify backup systems are healthy"
)
console.print(f" Created task: {test_task['id']}")
+2 -2
View File
@@ -207,7 +207,7 @@ def print_help() -> None:
console.print()
console.print("[yellow]SET SCHEDULE:[/yellow]")
console.print(' set schedule @seed "Run audit" daily 04:00')
console.print(' set schedule @seedgo "Run audit" daily 04:00')
console.print(' set schedule @vera "Heartbeat" interval 240')
console.print(' set schedule @flow "Check plans" hourly 30')
console.print(" [dim]Types: daily HH:MM, hourly MM, interval MINUTES[/dim]")
@@ -264,7 +264,7 @@ def _handle_set_reminder(args: List[str]) -> bool:
date_str = args[0]
message = args[1]
target_branch = "@dev_central" # Default reminder target
target_branch = "@devpulse" # Default reminder target
# Parse --to flag
if "--to" in args:
+2 -2
View File
@@ -155,7 +155,7 @@ def _print_help() -> None:
console.print("[yellow]CREATE OPTIONS:[/yellow]")
console.print(" --due (Required) Due date: 1d, 7d, 2w, 1m, or ISO date (2026-02-15)")
console.print(" --to (Required) Recipient branch (e.g., @flow, @seed)")
console.print(" --to (Required) Recipient branch (e.g., @flow, @seedgo)")
console.print(" --message (Optional) Additional details for the follow-up")
console.print()
@@ -164,7 +164,7 @@ def _print_help() -> None:
console.print(' schedule create "Check FPLAN-0290 status" --due 7d --to @flow')
console.print()
console.print(' # Follow up with Seed about code review in 2 weeks')
console.print(' schedule create "Code review follow-up" --due 2w --to @seed --message "Review PR #45"')
console.print(' schedule create "Code review follow-up" --due 2w --to @seedgo --message "Review PR #45"')
console.print()
console.print(' # Check all due tasks and send reminder emails')
console.print(' schedule run-due')
+1 -1
View File
@@ -32,7 +32,7 @@ PLUGIN_CONFIG Schema:
"time": str | None, # For daily: "HH:MM" (24h). For hourly: minute "MM"
"interval_minutes": int | None, # For interval: minutes between runs
"enabled": bool, # Plugin active/inactive toggle
"branch": str, # Target branch email (e.g., "@seed")
"branch": str, # Target branch email (e.g., "@seedgo")
"fresh": bool, # True = fresh session, False = resume
"max_turns": int, # Max agent turns (safety limit)
"prompt": str, # What the spawned agent should do
@@ -9,9 +9,9 @@
"""
Daily Standards Audit Plugin
Wakes @seed daily at 04:00 with fresh context to run a full system audit.
Seed checks BRANCH_REGISTRY completeness, runs drone @seed audit @all,
fixes non-compliance issues, and emails a summary to @dev_central.
Wakes @seedgo daily at 04:00 with fresh context to run a full system audit.
Seed checks BRANCH_REGISTRY completeness, runs drone @seedgo audit @all,
fixes non-compliance issues, and emails a summary to @devpulse.
"""
from aipass.prax import logger
@@ -22,15 +22,15 @@ PLUGIN_CONFIG = {
"time": "04:00",
"interval_minutes": None,
"enabled": True,
"branch": "@seed",
"branch": "@seedgo",
"fresh": True,
"max_turns": 50,
"prompt": (
"Daily maintenance audit. "
"1) Read BRANCH_REGISTRY.json - confirm all branches are registered and paths exist. "
"2) Run drone @seed audit @all - check standards compliance across all branches. "
"2) Run drone @seedgo audit @all - check standards compliance across all branches. "
"3) Fix any non-compliance issues you can fix directly. "
"4) Email summary to @dev_central with: branches audited, pass/fail counts, "
"4) Email summary to @devpulse with: branches audited, pass/fail counts, "
"issues found, issues fixed, remaining issues. "
"5) Update your memories with audit results."
),
+1 -1
View File
@@ -228,7 +228,7 @@ def _process_single_task(task: dict, results: dict) -> None:
message=email_body,
from_branch='@daemon',
auto_execute=True,
reply_to='@dev_central',
reply_to='@devpulse',
)
if email_sent:
@@ -57,7 +57,7 @@ class TestCreate:
name="test_audit",
action_type="schedule",
schedule_type="daily",
target_branch="@seed",
target_branch="@seedgo",
prompt="Run audit",
time="04:00",
fresh=True,
@@ -68,7 +68,7 @@ class TestCreate:
assert action["type"] == "schedule"
assert action["schedule_type"] == "daily"
assert action["time"] == "04:00"
assert action["target_branch"] == "@seed"
assert action["target_branch"] == "@seedgo"
assert action["enabled"] is True
assert action["last_run"] is None
assert action["completed"] is None
@@ -88,7 +88,7 @@ class TestCreate:
name="Check VERA progress",
action_type="reminder",
schedule_type="once",
target_branch="@dev_central",
target_branch="@devpulse",
prompt="Check VERA progress",
due_date="2026-03-11",
)
@@ -174,11 +174,11 @@ class TestCreateTask:
task = create_task(
task="Check backup health",
due_date="7d",
recipient="@dev_central",
recipient="@devpulse",
message="Verify backup systems",
)
assert task["task"] == "Check backup health"
assert task["recipient"] == "@dev_central"
assert task["recipient"] == "@devpulse"
assert task["message"] == "Verify backup systems"
assert task["status"] == "pending"
assert len(task["id"]) == 16
@@ -192,7 +192,7 @@ class TestCreateTask:
create_task(
task="persisted task",
due_date="1d",
recipient="@seed",
recipient="@seedgo",
message="msg",
)
raw = json.loads(isolate_registry.read_text(encoding="utf-8"))
@@ -25,6 +25,7 @@ _PKG_ROOT = Path(__file__).resolve().parents[4]
# Standard imports
import json
import shutil
from datetime import datetime, timezone
from typing import Dict, List, Any
@@ -421,8 +422,8 @@ def archive_plan(plan_path: Path) -> bool:
# Store source path for verification
source_path = Path(plan_path)
# Attempt move
plan_path.rename(destination)
# Attempt move (shutil.move handles cross-filesystem moves)
shutil.move(str(plan_path), str(destination))
# VERIFICATION LAYER: Confirm move actually happened
if not destination.exists():
@@ -228,6 +228,15 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False,
from aipass.flow.apps.handlers.mbank.process import archive_plan
if archive_plan(plan_file):
logger.info(f"[{MODULE_NAME}] Cleaned up orphaned file for {plan_label}: {plan_file}")
# Update registry flags that were missed on the failed first close
plan_info["processed"] = True
plan_info["processed_date"] = datetime.now(timezone.utc).isoformat()
plan_info["cleanup_completed"] = True
plan_info["cleanup_date"] = datetime.now(timezone.utc).isoformat()
if reg_file:
save_registry(registry, registry_file=reg_file)
else:
save_registry(registry)
messages.append({"type": "success", "text": " Orphaned file archived successfully"})
else:
logger.warning(f"[{MODULE_NAME}] Failed to archive orphaned file for {plan_label}: {plan_file}")
+1 -1
View File
@@ -104,7 +104,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
+17 -1
View File
@@ -134,5 +134,21 @@
"DPLAN-0068_self_audit_compliance_sprint_2026-03-27.md": "2026-03-27T23:22:05.681987",
"DPLAN-0070_self_audit_compliance_sprint_2026-03-27.md": "2026-03-27T23:22:41.790812",
"DPLAN-0066_self_audit_compliance_sprint_2026-03-27.md": "2026-03-27T23:22:41.790824",
"DPLAN-0072_self_audit_compliance_sprint_2026-03-28.md": "2026-03-28T00:32:51.757552"
"DPLAN-0072_self_audit_compliance_sprint_2026-03-28.md": "2026-03-28T00:32:51.757552",
"DPLAN-0052_branch_audit_sprint_silent_catch_wave_2026-03-23.md": "2026-03-28T12:27:09.971335",
"DPLAN-0055_persistent_citizen_git_branches_2026-03-24.md": "2026-03-28T12:27:25.886644",
"DPLAN-0057_night_shift_drone_compliance_branch_audits_sy_2026-03-24.md": "2026-03-28T12:27:38.467210",
"DPLAN-0063_self_audit_compliance_sprint_2026-03-27.md": "2026-03-28T12:27:58.368678",
"DPLAN-0064_self_audit_compliance_sprint_2026-03-27.md": "2026-03-28T12:28:15.007738",
"DPLAN-0065_self_audit_compliance_sprint_2026-03-27.md": "2026-03-28T12:28:31.421009",
"DPLAN-0069_100_seedgo_compliance_sprint_2026-03-27.md": "2026-03-28T12:28:47.782884",
"DPLAN-0071_self_audit_compliance_sprint_2026-03-28.md": "2026-03-28T12:29:04.294942",
"FPLAN-0134_persistent_citizen_git_branches_drone_build_2026-03-24.md": "2026-03-28T19:43:55.209451",
"DPLAN-0018_commons_bug_investigation_fts5_search_visitor_2026-03-17.md": "2026-03-28T19:44:25.813768",
"DPLAN-0019_module_rename_remove__module_suffix_from_comm_2026-03-17.md": "2026-03-28T19:44:44.216623",
"DPLAN-0054_bypass_accountability_tracker_2026-03-23.md": "2026-03-28T19:51:55.904984",
"DPLAN-0059_seedgo_test_quality_standard_deep_coverage_en_2026-03-24.md": "2026-03-28T19:52:31.066363",
"DPLAN-0060_custom_function_scanner_seedgo_module_for_bra_2026-03-24.md": "2026-03-28T19:52:49.501398",
"DPLAN-0061_expand_test_quality_checker_to_all_standard_c_2026-03-24.md": "2026-03-28T19:53:13.017860",
"FPLAN-0138_s59_walkthrough_fixes_2026-03-28.md": "2026-03-28T19:53:32.097935"
}
+5
View File
@@ -406,6 +406,11 @@
"file": "apps/",
"standard": "architecture",
"reason": "Template files managed by spawn — prax cannot add/remove template files. Template structure is defined in spawn/templates/builder/ and applied at branch creation time."
},
{
"file": "apps/handlers/monitoring/event_queue.py",
"standard": "silent_catch",
"reason": "queue.Empty is expected control flow from PriorityQueue.get(timeout=N), not an error. Logging it produces 144,000 noise entries per 4hrs of monitoring (10 polls/sec). The empty state IS the normal idle state."
}
],
"notes": {
+1 -1
View File
@@ -104,7 +104,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
@@ -60,7 +60,7 @@ def get_calling_module() -> str:
"""Detect calling module from stack trace
Returns:
Module name (e.g., 'drone', 'flow', 'cortex') or 'unknown_module'
Module name (e.g., 'drone', 'flow', 'spawn') or 'unknown_module'
"""
caller_path = _find_external_caller_path()
if caller_path:
@@ -79,7 +79,6 @@ class MonitoringQueue:
try:
return self.queue.get(timeout=timeout)
except Empty:
logger.info("[event_queue] Queue empty on dequeue poll")
return None
def flush(self):
@@ -89,7 +88,6 @@ class MonitoringQueue:
try:
self.queue.get_nowait()
except Empty:
logger.info("[event_queue] Flush complete (queue drained)")
break
self.recent_events.clear()
@@ -254,12 +254,12 @@ class LogFileWatcher(FileSystemEventHandler):
elif "search" in log_line.lower() and "query" in log_line.lower():
return {'command': "memory_bank search", 'caller': None, 'target': None}
# Pattern 10: Cortex operations (direct python3 calls)
if "[cortex]" in log_line.lower():
# Pattern 10: Spawn operations (direct python3 calls)
if "[spawn]" in log_line.lower():
if "Creating" in log_line and "branch" in log_line.lower():
match = re.search(r"Creating\s+(?:branch\s+)?(\w+)", log_line)
target = match.group(1).upper() if match else None
return {'command': "cortex create branch", 'caller': None, 'target': target}
return {'command': "spawn create branch", 'caller': None, 'target': target}
# Pattern 11: Trigger operations (direct python3 calls)
if "[trigger]" in log_line.lower():
+5 -1
View File
@@ -77,6 +77,10 @@ def _guard_branch_access():
return # Allow command-line Python through
return # Allow if truly can't determine
# Allow pytest — test files need to import handlers for testing
if "pytest" in caller_file or "/_pytest/" in caller_file:
return
# Check if caller is from our branch
# MY_BRANCH is "aipass.seedgo" (dotted), but filesystem uses "/aipass/seedgo/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
@@ -104,7 +108,7 @@ def _guard_branch_access():
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
+9 -2
View File
@@ -645,11 +645,18 @@ def test_get_json_path_returns_pathlib_path(tmp_path: Path) -> None:
def test_ensure_no_overwrite_existing(tmp_path: Path) -> None:
"""no_overwrite: ensure_json_exists does not overwrite already_exists data."""
"""no_overwrite: ensure_json_exists does not overwrite already_exists valid data."""
json_dir = _json_dir_as_path(tmp_path)
json_dir.mkdir(parents=True, exist_ok=True)
target = json_dir / "preserve_config.json"
target.write_text('{"custom": "data"}', encoding="utf-8")
# Write a VALID config structure with an extra custom key
valid_config = {
"module_name": "preserve",
"version": "1.0.0",
"config": {"auto_save": True, "enabled": True},
"custom": "data",
}
target.write_text(json.dumps(valid_config), encoding="utf-8")
json_handler.ensure_json_exists("preserve", "config")
data = json.loads(target.read_text(encoding="utf-8"))
assert data.get("custom") == "data", "Must not overwrite existing file"
@@ -1,7 +1,7 @@
{
"metadata": {
"version": "1.0.0",
"last_updated": "2026-03-25",
"last_updated": "2026-03-28",
"description": "Template file tracking registry for ID-based updates"
},
"files": {
@@ -17,6 +17,12 @@
"content_hash": "c9702fe2cc21",
"has_branch_placeholder": false
},
"f015": {
"path": ".aipass/README.md",
"name": "README.md",
"content_hash": "f42d87684fdf",
"has_branch_placeholder": false
},
"f003": {
"path": ".aipass/aipass_local_prompt.md",
"name": "aipass_local_prompt.md",
@@ -29,6 +35,12 @@
"content_hash": "93d3fcb74f23",
"has_branch_placeholder": false
},
"f025": {
"path": ".claude/README.md",
"name": "README.md",
"content_hash": "adb0ce8c53c1",
"has_branch_placeholder": false
},
"f005": {
"path": ".claude/settings.local.json",
"name": "settings.local.json",
@@ -41,12 +53,24 @@
"content_hash": "841dedb922da",
"has_branch_placeholder": false
},
"f027": {
"path": ".seedgo/README.md",
"name": "README.md",
"content_hash": "ea03468bbf16",
"has_branch_placeholder": false
},
"f007": {
"path": ".seedgo/bypass.json",
"name": "bypass.json",
"content_hash": "0ac90a35515b",
"has_branch_placeholder": false
},
"f028": {
"path": ".trinity/README.md",
"name": "README.md",
"content_hash": "f461c9b16fc5",
"has_branch_placeholder": false
},
"f008": {
"path": ".trinity/local.json",
"name": "local.json",
@@ -83,102 +107,18 @@
"content_hash": "059295ad4d4e",
"has_branch_placeholder": false
},
"f014": {
"path": "apps/__init__.py",
"name": "__init__.py",
"content_hash": "41b011f487af",
"has_branch_placeholder": false
},
"f017": {
"path": "apps/handlers/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
"has_branch_placeholder": false
},
"f018": {
"path": "apps/{{BRANCH}}.py",
"name": "{{BRANCH}}.py",
"content_hash": "fc71b424c10b",
"has_branch_placeholder": true
},
"f019": {
"path": "artifacts/birth_certificate.json",
"name": "birth_certificate.json",
"content_hash": "1ec401f4e397",
"has_branch_placeholder": false
},
"f020": {
"path": "docs/README.md",
"name": "README.md",
"content_hash": "2434da568727",
"has_branch_placeholder": false
},
"f021": {
"path": "dropbox/README.md",
"name": "README.md",
"content_hash": "9e1e9b71f93b",
"has_branch_placeholder": false
},
"f022": {
"path": "pytest.ini",
"name": "pytest.ini",
"content_hash": "7b39ba7bca40",
"has_branch_placeholder": false
},
"f023": {
"path": "tests/__init__.py",
"name": "__init__.py",
"content_hash": "881f06bb6574",
"has_branch_placeholder": false
},
"f026": {
"path": "{{BRANCH}}_json/README.md",
"name": "README.md",
"content_hash": "e64fa555e7b8",
"has_branch_placeholder": false
},
"f016": {
"path": "apps/modules/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
"has_branch_placeholder": false
},
"f024": {
"path": "tests/conftest.py",
"name": "conftest.py",
"content_hash": "5d98b049957b",
"has_branch_placeholder": false
},
"f015": {
"path": ".aipass/README.md",
"name": "README.md",
"content_hash": "f42d87684fdf",
"has_branch_placeholder": false
},
"f025": {
"path": ".claude/README.md",
"name": "README.md",
"content_hash": "adb0ce8c53c1",
"has_branch_placeholder": false
},
"f027": {
"path": ".seedgo/README.md",
"name": "README.md",
"content_hash": "ea03468bbf16",
"has_branch_placeholder": false
},
"f028": {
"path": ".trinity/README.md",
"name": "README.md",
"content_hash": "f461c9b16fc5",
"has_branch_placeholder": false
},
"f029": {
"path": "apps/README.md",
"name": "README.md",
"content_hash": "92a956009e0e",
"has_branch_placeholder": false
},
"f014": {
"path": "apps/__init__.py",
"name": "__init__.py",
"content_hash": "41b011f487af",
"has_branch_placeholder": false
},
"f030": {
"path": "apps/handlers/README.md",
"name": "README.md",
@@ -191,17 +131,23 @@
"content_hash": "a4cf0a8e3b4f",
"has_branch_placeholder": false
},
"f033": {
"path": "apps/modules/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
"has_branch_placeholder": false
},
"f032": {
"path": "apps/plugins/README.md",
"name": "README.md",
"content_hash": "d1e4e2b98c38",
"has_branch_placeholder": false
},
"f033": {
"path": "apps/plugins/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
"has_branch_placeholder": false
"f018": {
"path": "apps/{{BRANCH}}.py",
"name": "{{BRANCH}}.py",
"content_hash": "fc71b424c10b",
"has_branch_placeholder": true
},
"f034": {
"path": "artifacts/README.md",
@@ -209,6 +155,18 @@
"content_hash": "de20d11e5cfd",
"has_branch_placeholder": false
},
"f019": {
"path": "artifacts/birth_certificate.json",
"name": "birth_certificate.json",
"content_hash": "1ec401f4e397",
"has_branch_placeholder": false
},
"f020": {
"path": "docs/README.md",
"name": "README.md",
"content_hash": "2434da568727",
"has_branch_placeholder": false
},
"f035": {
"path": "docs.local/README.md",
"name": "README.md",
@@ -221,12 +179,24 @@
"content_hash": "e3e5a6b9c9c5",
"has_branch_placeholder": false
},
"f021": {
"path": "dropbox/README.md",
"name": "README.md",
"content_hash": "9e1e9b71f93b",
"has_branch_placeholder": false
},
"f037": {
"path": "logs/README.md",
"name": "README.md",
"content_hash": "4ca207af6bd3",
"has_branch_placeholder": false
},
"f022": {
"path": "pytest.ini",
"name": "pytest.ini",
"content_hash": "7b39ba7bca40",
"has_branch_placeholder": false
},
"f038": {
"path": "templates/README.md",
"name": "README.md",
@@ -239,17 +209,59 @@
"content_hash": "c157895c9b27",
"has_branch_placeholder": false
},
"f023": {
"path": "tests/__init__.py",
"name": "__init__.py",
"content_hash": "881f06bb6574",
"has_branch_placeholder": false
},
"f024": {
"path": "tests/conftest.py",
"name": "conftest.py",
"content_hash": "5d98b049957b",
"has_branch_placeholder": false
},
"f040": {
"path": "tools/README.md",
"name": "README.md",
"content_hash": "3c7eaedb16ac",
"has_branch_placeholder": false
},
"f026": {
"path": "{{BRANCH}}_json/README.md",
"name": "README.md",
"content_hash": "e64fa555e7b8",
"has_branch_placeholder": false
},
"f041": {
"path": "{{BRANCH}}_json/custom_config/README.md",
"name": "README.md",
"content_hash": "28e9ae373563",
"has_branch_placeholder": false
},
"f017": {
"path": "apps/handlers/__init__.py",
"name": "__init__.py",
"content_hash": "55afc5eddbe0",
"has_branch_placeholder": false
},
"f016": {
"path": ".spawn/.registry_ignore.json",
"name": ".registry_ignore.json",
"content_hash": "34f5e7ff7e01",
"has_branch_placeholder": false
},
"f042": {
"path": ".spawn/README.md",
"name": "README.md",
"content_hash": "e22ad5337efd",
"has_branch_placeholder": false
},
"f043": {
"path": "apps/plugins/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
"has_branch_placeholder": false
}
},
"directories": {
@@ -318,11 +330,26 @@
"name": "docs",
"has_branch_placeholder": false
},
"d019": {
"path": "docs.local",
"name": "docs.local",
"has_branch_placeholder": false
},
"d020": {
"path": "docs.local/sub_agent_drops",
"name": "sub_agent_drops",
"has_branch_placeholder": false
},
"d014": {
"path": "dropbox",
"name": "dropbox",
"has_branch_placeholder": false
},
"d021": {
"path": "logs",
"name": "logs",
"has_branch_placeholder": false
},
"d015": {
"path": "templates",
"name": "templates",
@@ -343,25 +370,15 @@
"name": "{{BRANCH}}_json",
"has_branch_placeholder": true
},
"d019": {
"path": "docs.local",
"name": "docs.local",
"has_branch_placeholder": false
},
"d020": {
"path": "docs.local/sub_agent_drops",
"name": "sub_agent_drops",
"has_branch_placeholder": false
},
"d021": {
"path": "logs",
"name": "logs",
"has_branch_placeholder": false
},
"d022": {
"path": "{{BRANCH}}_json/custom_config",
"name": "custom_config",
"has_branch_placeholder": false
},
"d023": {
"path": ".spawn",
"name": ".spawn",
"has_branch_placeholder": false
}
}
}
@@ -0,0 +1,88 @@
"""{{BRANCHNAME}} handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.{{BRANCH}}"
def _find_real_caller():
"""Walk the stack to find the actual file that triggered this import.
Skips this file, importlib internals, and frozen modules.
Returns tuple: (file_path, import_line) or (None, None).
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
if this_file in str(Path(filename).resolve()):
continue
if filename.startswith("<") or "importlib" in filename:
continue
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""Block cross-branch handler imports.
Only code from within the '{{BRANCH}}' branch can import these handlers.
External branches must use aipass.{{BRANCH}}.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
if caller_file is None:
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
return
return
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" For full standards guide:\n"
f" drone @seedgo handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()
+30
View File
@@ -1,11 +1,41 @@
"""Shared test fixtures for spawn test suite."""
import json
import shutil
import pytest
from pathlib import Path
from unittest.mock import patch, MagicMock
# ---------------------------------------------------------------------------
# Registry backup/restore — prevents test ghost entries in AIPASS_REGISTRY.json
# ---------------------------------------------------------------------------
def _find_registry_path() -> Path:
"""Locate AIPASS_REGISTRY.json from the spawn branch."""
return Path(__file__).resolve().parents[4] / "AIPASS_REGISTRY.json"
@pytest.fixture(autouse=True, scope="session")
def _protect_registry():
"""Backup AIPASS_REGISTRY.json before the test session, restore after.
Prevents tests that call spawn_agent/grant_passport without a
registry_path override from permanently polluting the real registry.
"""
reg = _find_registry_path()
backup = reg.with_suffix(".json.test_backup")
if reg.exists():
shutil.copy2(reg, backup)
yield
if backup.exists():
shutil.copy2(backup, reg)
backup.unlink()
@pytest.fixture
def sample_data():
"""Pre-populated JSON test data for spawn operations."""
@@ -297,7 +297,7 @@ def extract_mentions(content: str) -> List[str]:
conn = get_db()
placeholders = ",".join("?" * len(mentioned))
query = f"SELECT branch_name FROM agents WHERE LOWER(branch_name) IN ({placeholders})"
query = f"SELECT DISTINCT LOWER(branch_name) FROM agents WHERE LOWER(branch_name) IN ({placeholders})"
rows = conn.execute(query, mentioned).fetchall()
close_db(conn)