fix: post-Phase-0 corrections — data-dir bind mounts, reserved subdomains, list_active()
Unit Tests / test (push) Successful in 11m31s

Three related fixes discovered during review of Phase 0 and Phase 1 manifests:

1. validate_rendered_compose(): add allowed_data_dir param. After ${PIC_DATA_DIR}
   substitution, compose templates produce absolute paths; without this the
   validator would reject every service install.  ServiceComposer.write_compose()
   now passes its resolved data_dir so only the designated data directory is
   exempt — /etc, /proc, docker.sock etc. still blocked.

2. _RESERVED_SUBDOMAINS: remove service-level subdomains (mail, calendar, files,
   webdav, webmail). The reserved list should protect PIC infrastructure endpoints
   (api, webui, admin) — not service subdomains that official store services
   (calendar, files, webmail) must be allowed to claim.  Aligns with the
   existing _RESERVED_SUBS in service_registry.py.

3. ServiceRegistry.list_active(): new method returning only installed store
   services (no builtins). This is the forward-looking API that Phase 2 will
   make the primary read path once builtins are deleted. Adding it now unblocks
   the QA agent's test_optional_services_feature.py which was already testing
   the expected Phase 2 behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 07:35:43 -04:00
parent c40919d374
commit 18b50d08c1
5 changed files with 1145 additions and 9 deletions
+14
View File
@@ -92,6 +92,20 @@ class ServiceRegistry:
return None
return {**manifest, 'config': self._merged_config(manifest)}
def list_active(self) -> List[Dict]:
"""Return only installed store services, each with merged config.
Unlike list_all(), builtins are excluded. Use this wherever the
intent is "what has the admin chosen to run?" rather than "everything
the registry knows about."
"""
results = []
for _svc_id, record in self._cm.get_installed_services().items():
manifest = record.get('manifest') or {}
if manifest.get('id'):
results.append({**manifest, 'config': self._merged_config(manifest)})
return results
def list_all(self) -> List[Dict]:
"""
Return all services — builtins first, then installed store services —