Model transport provider sources

This commit is contained in:
jwinterm
2026-07-08 10:12:59 -04:00
parent e272ec1afb
commit f0d26933e6
10 changed files with 148 additions and 8 deletions
+1
View File
@@ -1634,6 +1634,7 @@ def _print_transport_status(status: TransportStatus) -> None:
f"supervisor={status.supervisor} "
f"adapter={status.adapter} "
f"backend={status.backend} "
f"provider_source={status.provider_source} "
f"installed={_bool(status.installed)} "
f"running={_bool(status.running)} "
f"adoptable={_bool(status.adoptable)} "
+4
View File
@@ -73,6 +73,10 @@ def _daemon_adapters_doc() -> str:
sections.append(f"- Protocols: {', '.join(adapter.protocols)}")
sections.append(f"- Default policy: `{adapter.default_policy}`")
sections.append(f"- Generated artifacts: {', '.join(adapter.generated_artifacts)}")
sections.append(
"- Provider sources: "
+ (", ".join(f"`{source}`" for source in adapter.provider_sources) or "-")
)
sections.append("")
sections.append("Notes:")
for note in adapter.notes:
+14
View File
@@ -31,6 +31,7 @@ class DaemonAdapter:
default_policy: str
generated_artifacts: tuple[str, ...]
notes: tuple[str, ...]
provider_sources: tuple[str, ...] = ()
@dataclass(frozen=True)
@@ -512,6 +513,7 @@ DAEMON_ADAPTERS = (
"Clearnet defaults to adopt because TLS and public vhost policy are operator-owned.",
"Tor/I2P HTTP targets can use loopback-only server blocks.",
),
provider_sources=("configured", "system-adopted", "system-managed", "platform-package"),
),
DaemonAdapter(
daemon=DEFAULT_DAEMONS["tor"],
@@ -522,6 +524,7 @@ DAEMON_ADAPTERS = (
"AMPG should preserve existing HiddenServiceDir material when adopting.",
"Managed hidden-service keys belong under AMPG state.",
),
provider_sources=("configured", "bundled-sidecar", "system-adopted", "system-managed", "platform-package"),
),
DaemonAdapter(
daemon=DEFAULT_DAEMONS["i2p"],
@@ -532,6 +535,7 @@ DAEMON_ADAPTERS = (
"Web tunnels should use web-specific key files.",
"Existing RPC/P2P tunnel keys are not reused for web publishing.",
),
provider_sources=("configured", "bundled-sidecar", "system-adopted", "system-managed", "platform-package"),
),
DaemonAdapter(
daemon=DEFAULT_DAEMONS["gemini"],
@@ -542,6 +546,7 @@ DAEMON_ADAPTERS = (
"Gemini serves generated Gemtext output directly.",
"Certificate and key paths are plan values until install/apply support exists.",
),
provider_sources=("configured", "bundled-sidecar", "system-adopted", "system-managed", "platform-package"),
),
DaemonAdapter(
daemon=DEFAULT_DAEMONS["ipfs"],
@@ -552,6 +557,7 @@ DAEMON_ADAPTERS = (
"IPFS output is static web content for a local gateway or later pinning.",
"IPFS is content-addressed distribution, not an anonymity layer.",
),
provider_sources=("configured", "bundled-sidecar", "system-adopted", "system-managed", "platform-package"),
),
DaemonAdapter(
daemon=DEFAULT_DAEMONS["reticulum"],
@@ -563,5 +569,13 @@ DAEMON_ADAPTERS = (
"Reticulum is resilient/private routing, not an anonymity layer.",
"Physical interfaces may require explicit operator setup.",
),
provider_sources=(
"configured",
"bundled-sidecar",
"system-adopted",
"system-managed",
"platform-package",
"operator-interface",
),
),
)
+18
View File
@@ -40,6 +40,7 @@ class TransportStatus:
supervisor: str
adapter: str
backend: str
provider_source: str
installed: bool
running: bool
adoptable: bool
@@ -265,6 +266,11 @@ def _transport_status(
supervisor=provider.process_supervisor,
adapter=f"{adapter.protocol}/{adapter.daemon}",
backend=adapter.backend,
provider_source=_provider_source_for_status(
action=action,
installed=daemon.installed,
executable_path=daemon.executable_path,
),
installed=daemon.installed,
running=daemon.running,
adoptable=adoptable,
@@ -276,6 +282,18 @@ def _transport_status(
)
def _provider_source_for_status(*, action: str, installed: bool, executable_path: str | None) -> str:
if action == "adopt-existing":
return "system-adopted"
if action == "manage-owned":
if installed or executable_path:
return "system-managed"
return "platform-package"
if action == "render-only":
return "external"
return "unavailable"
def _process_running(executable: str) -> bool:
pgrep = shutil.which("pgrep")
if not pgrep:
+73
View File
@@ -3,6 +3,14 @@ from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class ProviderSource:
kind: str
discovery: str
lifecycle: str
notes: tuple[str, ...] = ()
@dataclass(frozen=True)
class TransportAdapter:
protocol: str
@@ -13,9 +21,48 @@ class TransportAdapter:
managed_supported: bool
default_ports: tuple[int, ...] = ()
generated_artifacts: tuple[str, ...] = ()
provider_sources: tuple[ProviderSource, ...] = ()
notes: tuple[str, ...] = ()
COMMON_SERVICE_PROVIDER_SOURCES = (
ProviderSource(
kind="configured",
discovery="explicit daemon config and approved generated snippets",
lifecycle="operator-owned unless daemon_policy selects manage",
notes=("Explicit config always wins over automatic discovery.",),
),
ProviderSource(
kind="system-adopted",
discovery="running system daemon, service, or listen endpoint",
lifecycle="operator-owned; AMPG may generate snippets but does not own the service",
notes=("Used when daemon_policy is adopt or auto with a healthy daemon.",),
),
ProviderSource(
kind="system-managed",
discovery="installed daemon binary on the selected platform",
lifecycle="AMPG-owned state and AMPG-named supervisor entry",
notes=("Used when daemon_policy is manage or auto and the platform can supervise it.",),
),
ProviderSource(
kind="platform-package",
discovery="allowlisted package backend such as apt, brew, or pkg",
lifecycle="installed only through reviewed deploy apply package stage",
notes=("Package install is separate from state, supervisor, start, address, and health stages.",),
),
)
USERSPACE_PROVIDER_SOURCES = COMMON_SERVICE_PROVIDER_SOURCES + (
ProviderSource(
kind="bundled-sidecar",
discovery="AMPG package or app resource provider directory",
lifecycle="AMPG-owned process and state",
notes=("Useful for old laptops, single-purpose boxes, and mobile-server shells.",),
),
)
TRANSPORT_ADAPTERS = (
TransportAdapter(
protocol="clearnet",
@@ -26,6 +73,7 @@ TRANSPORT_ADAPTERS = (
managed_supported=True,
default_ports=(80, 443),
generated_artifacts=("nginx server block snippet",),
provider_sources=COMMON_SERVICE_PROVIDER_SOURCES,
notes=(
"Clearnet ingress is usually adopted because TLS and public vhost policy are operator-owned.",
),
@@ -39,6 +87,7 @@ TRANSPORT_ADAPTERS = (
managed_supported=True,
default_ports=(18080,),
generated_artifacts=("torrc hidden-service snippet", "loopback HTTP server block"),
provider_sources=USERSPACE_PROVIDER_SOURCES,
notes=(
"Adopted Tor keeps existing HiddenServiceDir material.",
"Managed Tor stores onion service keys under AMPG state.",
@@ -53,6 +102,18 @@ TRANSPORT_ADAPTERS = (
managed_supported=False,
default_ports=(18080,),
generated_artifacts=("future Arti onion-service config",),
provider_sources=(
ProviderSource(
kind="bundled-sidecar",
discovery="AMPG package or app resource provider directory",
lifecycle="future AMPG-owned Arti onion-service process",
),
ProviderSource(
kind="configured",
discovery="explicit arti binary path and onion-service config",
lifecycle="future AMPG-owned or operator-owned by policy",
),
),
notes=("Modeled as a future backend until the AMPG Arti adapter is implemented.",),
),
TransportAdapter(
@@ -64,6 +125,7 @@ TRANSPORT_ADAPTERS = (
managed_supported=True,
default_ports=(18081,),
generated_artifacts=("i2pd server tunnel snippet", "loopback HTTP server block"),
provider_sources=USERSPACE_PROVIDER_SOURCES,
notes=("Destination keys are transport identity material and must be preserved.",),
),
TransportAdapter(
@@ -75,6 +137,7 @@ TRANSPORT_ADAPTERS = (
managed_supported=True,
default_ports=(1965,),
generated_artifacts=("Agate plan values",),
provider_sources=USERSPACE_PROVIDER_SOURCES,
notes=("Gemini serves generated Gemtext output directly.",),
),
TransportAdapter(
@@ -86,6 +149,7 @@ TRANSPORT_ADAPTERS = (
managed_supported=True,
default_ports=(5001, 8080),
generated_artifacts=("pin/add plan",),
provider_sources=USERSPACE_PROVIDER_SOURCES,
notes=("IPFS is content-addressed distribution, not an anonymity layer.",),
),
TransportAdapter(
@@ -97,6 +161,15 @@ TRANSPORT_ADAPTERS = (
managed_supported=True,
default_ports=(),
generated_artifacts=("Reticulum page-service plan",),
provider_sources=USERSPACE_PROVIDER_SOURCES
+ (
ProviderSource(
kind="operator-interface",
discovery="explicit Reticulum interface configuration",
lifecycle="operator-owned physical or link-layer interface",
notes=("AMPG can manage page serving while interface setup may remain manual.",),
),
),
notes=(
"Reticulum is resilient/private routing, not an anonymity layer.",
"Physical interfaces may require operator setup outside AMPG.",