Gate route fixtures by protocol tier

This commit is contained in:
jwinterm
2026-07-06 03:51:14 -04:00
parent 2ca73ca431
commit bd782ed7ed
5 changed files with 106 additions and 1 deletions
+29 -1
View File
@@ -9,6 +9,22 @@ from .config import GatewayConfig, ProtocolConfig, RoutePolicyConfig, SiteConfig
SCHEMA = "ampg.fixture-manifest.v1"
TIER_ORDER = {
"static": 0,
"interactive-lite": 1,
"identity": 2,
"transactional": 3,
"realtime": 4,
"internal": 5,
}
DEFAULT_MAX_TIERS = {
"clearnet": "realtime",
"tor": "transactional",
"i2p": "transactional",
"gemini": "interactive-lite",
"ipfs": "static",
"reticulum": "interactive-lite",
}
@dataclass(frozen=True)
@@ -27,7 +43,7 @@ def fixture_manifest(site: SiteConfig) -> dict[str, Any]:
fixtures.extend(
_fixture_entry(site, protocol, route_policy)
for route_policy in site.interactions.routes
if _route_is_public(route_policy)
if _route_is_public(route_policy) and _route_supported(protocol, route_policy)
)
return {
"schema": SCHEMA,
@@ -101,6 +117,18 @@ def _route_is_public(route_policy: RoutePolicyConfig) -> bool:
return route_policy.public_allowed and route_policy.tier != "internal"
def _route_supported(protocol: ProtocolConfig, route_policy: RoutePolicyConfig) -> bool:
max_tier = str(protocol.options.get("max_tier", DEFAULT_MAX_TIERS.get(protocol.name, "static")))
return _tier_rank(route_policy.tier) <= _tier_rank(max_tier)
def _tier_rank(tier: str) -> int:
try:
return TIER_ORDER[tier]
except KeyError as exc:
raise ValueError(f"unsupported interaction tier {tier!r}") from exc
def _interaction_policy(
protocol: ProtocolConfig,
route_policy: RoutePolicyConfig | None,
+8
View File
@@ -195,6 +195,14 @@ CONFIG_FIELDS = (
"strip",
"Script handling policy for privacy-html render targets.",
),
ConfigField(
"site.protocols.<name>",
"max_tier",
"enum",
False,
"transport default",
"Maximum interaction tier this protocol target may expose.",
),
)
+4
View File
@@ -65,3 +65,7 @@ not emitted.
Routes imported from `site.interactions.route_manifest` are treated the same way as inline
TOML routes.
AMPG also omits route fixtures whose interaction tier exceeds the protocol target's
`max_tier`. When `max_tier` is not configured, AMPG uses the transport capability defaults
from the generated interaction capability matrix.
+1
View File
@@ -28,3 +28,4 @@ This file is generated from code. Do not edit it by hand.
| site.protocols.<name> | `daemon_policy` | enum | no | protocol default | Whether AMPG adopts, manages, or only renders config. |
| site.protocols.<name> | `max_asset_bytes` | integer | no | 1048576 | Maximum asset size copied by privacy-html render targets. |
| site.protocols.<name> | `script_policy` | enum | no | strip | Script handling policy for privacy-html render targets. |
| site.protocols.<name> | `max_tier` | enum | no | transport default | Maximum interaction tier this protocol target may expose. |
+64
View File
@@ -303,6 +303,70 @@ renderer = "privacy-html"
with self.assertRaisesRegex(ValueError, "route manifest schema"):
load_config(config_path)
def test_manifest_omits_routes_above_protocol_max_tier(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
source = root / "site"
source.mkdir()
(source / "index.html").write_text("<h1>App</h1>", encoding="utf-8")
config_path = root / "gateway.toml"
config_path.write_text(
"""
[[site]]
id = "app"
domain = "app.test"
[site.source]
kind = "static-html"
path = "./site"
[site.outputs]
root = "./out"
plan_root = "./plan"
[[site.interactions.route]]
match = "/play/*"
tier = "interactive-lite"
[[site.interactions.route]]
match = "/checkout/*"
tier = "transactional"
[[site.interactions.route]]
match = "/live/*"
tier = "realtime"
realtime = true
[site.protocols.tor]
enabled = true
renderer = "privacy-html"
max_tier = "interactive-lite"
[site.protocols.gemini]
enabled = true
renderer = "gemtext"
[site.protocols.ipfs]
enabled = true
renderer = "clearnet"
cid = "bafyexample"
""",
encoding="utf-8",
)
config = load_config(config_path)
manifest = fixture_manifest(config.sites[0])
by_protocol = {}
for fixture in manifest["fixtures"]:
by_protocol.setdefault(fixture["protocol"], []).append(
fixture.get("route", {}).get("fixture_path", "/")
)
self.assertEqual(["/", "/play/"], by_protocol["tor"])
self.assertEqual(["/", "/play/"], by_protocol["gemini"])
self.assertEqual(["/"], by_protocol["ipfs"])
if __name__ == "__main__":
unittest.main()