Grimmory and MeTube published behind the family gate (controller >= 0.287.0, decisions 63/64), with complete records
gates / gates (push) Successful in 3s

- family_gate / family_gate_except / min_controller format (README, REUSE); gate family-gate + decoys
- templates/grimmory (v3.5.0, exceptions OPDS/Kobo/KOReader/Komga) + onboarding/grimmory.md
- templates/metube (2026.09.29, no exceptions; ladder .28 -> .29) + onboarding/metube.md; fixture MeTube
- volume-persistence gate: routed port read from the label NAME (R-801, red-proofed); APP_EXERCISE (R-788)
- box_walk: family_cookie; no cached "not gated" while an app has no router

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-02 09:36:49 +02:00
parent de0a9bd29f
commit 96829d0d0f
20 changed files with 1078 additions and 20 deletions
+16
View File
@@ -463,5 +463,21 @@ class TestEnvBuilding(unittest.TestCase):
self.assertEqual([f["env_var"] for f in cvp.parse_deploy_fields(felhom)], ["A"])
class TestRoutedPorts(unittest.TestCase):
def test_routed_ports_reads_the_mapping_form(self):
"""R-801: `compose config --format json` gives labels as a mapping; the port is in the KEY."""
resolved = {"services": {"metube": {"labels": {
"traefik.enable": "true",
"traefik.http.services.metube.loadbalancer.server.port": "8081"}}}}
self.assertEqual(cvp.routed_ports(resolved), [8081])
def test_routed_ports_list_form_still_read(self):
resolved = {"services": {"a": {"labels": ["traefik.http.services.a.loadbalancer.server.port=3000"]}}}
self.assertEqual(cvp.routed_ports(resolved), [3000])
def test_no_routed_port(self):
self.assertEqual(cvp.routed_ports({"services": {"db": {"labels": {"x": "y"}}}}), [])
if __name__ == "__main__":
unittest.main(verbosity=2)