Date: 2026-03-05
Status: Complete
PRD: MEMORY/WORK/20260305-000000_pai-pm-module-integration/PRD.md
Integrated the PAI Project Manager TypeScript/Bun server (scripts/pai/pm/) as a Python module (src/codomyrmex/pai_pm/) following the aider module pattern. Fixed two P0 security gaps in trust_gateway.py. Created 57 zero-mock tests (all passing).
Fix 1: Trust ledger atomic write + chmod 0600
trust_gateway.py:_save() was using write_text() directly β world-readable during write and no permission restriction. Fixed with .tmp β rename pattern + chmod(0o600) before rename.
Fix 2: Threading lock for _pending_confirmations
_pending_confirmations was accessed across threads without a lock. Added _confirmations_lock = threading.Lock() and wrapped the confirmation block in trusted_call_tool() with with _confirmations_lock:. Renamed cleanup function to _cleanup_expired_confirmations_locked() with docstring making clear it must be called with lock held.
Bonus fix: SyntaxError in environment_setup/dependency_resolver.py:119
A dangling try: block (missing except ImportError: for the import tomllib guard) caused pkgutil.walk_packages to raise SyntaxError during the full module scan, preventing TrustRegistry from being instantiated in tests. Fixed by adding the correct except ImportError: + early return.
Python files created:
exceptions.pyβ 5-class hierarchy (PaiPmErrorβ 4 subtypes)config.pyβPaiPmConfigdataclass with 5 env-var-backed fieldsserver_manager.pyβPaiPmServerManagerwith subprocess lifecycle, PID file (0o600), env allowlistclient.pyβPaiPmClientstdlib HTTP (no extra deps), 8 endpoint methodsmcp_tools.pyβ 6@mcp_tooldefinitions (auto-discovered)__init__.pyβ exports +HAS_BUNflag
TypeScript source:
Copied (rsync, excluding node_modules/) from scripts/pai/pm/ to src/codomyrmex/pai_pm/server/.
pyproject.toml ([tool.pytest.ini_options]): Added pai_pm: marker.
pyproject.toml: Added [project.optional-dependencies.pai_pm] (no Python deps, requires bun).
RASP docs created: README.md, SPEC.md, AGENTS.md, PAI.md.
-
_cleanup_expired_confirmations_locked()naming β "locked" suffix convention (used in CPython itself) signals to callers that the lock must already be held, preventing accidental double-acquisition deadlock. -
Atomic write with
.tmp+ rename β OS guarantees rename is atomic. Setting permissions on.tmpbefore rename eliminates the brief readable window. -
_build_safe_env()allowlist vs blocklist β Allowlist approach (only PAI_, GOOGLE_, AGENTMAIL_*, common system vars) is safer than a blocklist. Blocklist grows with each new API key env var. -
stdlib HTTP only in
client.pyβurllib.requestinstead ofrequestsorhttpx. Zero new Python dependencies for a module that wraps a TypeScript server. -
rsyncovergit mvβ Movingnode_modules/via git would pollute history with hundreds of files.rsync --exclude='node_modules/'copies only source files.
| Area | Tests |
|---|---|
| Security (trust_gateway) | 8 |
| pai_pm exceptions | 9 |
| pai_pm config | 12 |
| pai_pm server_manager | 17 |
| pai_pm client | 7 |
| pai_pm MCP tools | 8 |
| Total | 57 |
All tests pass. Ruff zero violations.