Skip to content

Commit c66d249

Browse files
author
codex-release
committed
flows: prove the degrade with the door genuinely absent, and correct what the gate cannot close
Three tests in test_no_meetings.py were proved against a stand-in for the absence rather than the absence, and one of them could not fail: * `test_an_unnamed_meetings_door_does_not_stop_the_process_booting` read `missing_doors()` in a process that NAMES the door — conftest.OFFLINE_DOORS declares VEXA_FLOWS_GATEWAY_URL for the whole suite, so the assertion held whatever the class was, and held on the tree this change fixes. It now delenvs the door and also calls preflight(), which is the actual boot. * the terminal-state contract passed the engine a hand-written `lambda d: d != "meetings"`. That proves the ENGINE degrades and says nothing about whether `domain_present` reads the configuration — the half that ships. It now passes `common.domain_present` itself over the emptied door. * the import defect was asserted by parsing meeting.py's source, because an import that already succeeded cannot be asked whether it would have. A new test asks a process that genuinely has none: a fresh interpreter with the door removed from the environment, importing the whole production step vocabulary. On the tip that command raises ConfigError THROUGH the import statement; here it exits 0. Three counts, measured instead of assumed: the agent domain declares THIRTEEN steps (test_no_agents.AGENT_STEPS), not eleven, and meeting.py has ELEVEN meetings_door() call sites, not twelve — asserted `== 11` now, not `>= 11`, since a floor is not a count. AND THE GATE ENTRY DOES NOT CLOSE. scripts/domain-doors.allow.json said this class change would remove flows' VEXA_FLOWS_GATEWAY_URL entry. It does not, and the checker is right: `judge()` refuses a DOMAIN naming an EDGE unconditionally, before it looks at any class — the capability escape hatch is a domain→domain rule, and doorOwner() resolves this key to the gateway. The entry closes on the de-hop, which is not a rename: meeting-api's POST /bots derives the per-user concurrent-bot cap from the gateway's X-User-Limits header (core/meetings/services/meeting-api/src/meeting_api/bot_spawn/router.py:208 `_resolve_max_concurrent`), so calling meeting-api directly without carrying that forward drops the cap silently. The ruling now says that in place of a promise the gate cannot keep. gate:domain-doors before and after this branch: 91 sites · 0 violations · 31 allowlisted · 0 stale, unchanged. The four refreshed `line` values are informational (`--refresh`); three moved because this branch edited common.py, and the gateway adapters one had already drifted on the tip.
1 parent 4ce1f8b commit c66d249

4 files changed

Lines changed: 52 additions & 11 deletions

File tree

core/flows/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ terminal, with the reason on the reaction, never a retry loop against a door tha
111111

112112
| domain | key | steps that declare it |
113113
|---|---|---|
114-
| agent | `VEXA_FLOWS_AGENT_API_URL` | eleven, listed in `tests/test_no_agents.py` |
114+
| agent | `VEXA_FLOWS_AGENT_API_URL` | thirteen, listed in `tests/test_no_agents.py` |
115115
| meetings | `VEXA_FLOWS_GATEWAY_URL` | `await_start` · `dispatch_bot` · `run_meeting` · `process_meeting` · `email_minutes` · `email_attendees` · `drop_to_attendees` · `prepare_meeting` |
116116

117117
**Presence is a configuration fact, never a probe.** A health check would make *"meeting-api is
@@ -127,6 +127,11 @@ never at import (`flows_steps.common.meetings_door`): `from .common import GATEW
127127
refusal while `flows_steps/meeting.py` was still loading, so an unset door was an ImportError for
128128
the whole step vocabulary, including every step with no interest in meetings.
129129

130+
**The class change does not close flows' `gate:domain-doors` entry, and the gate is right.** That
131+
gate refuses a domain naming an EDGE before it looks at any class, so the entry closes on the
132+
de-hop above and on nothing else; `scripts/domain-doors.allow.json` now says so, in place of a
133+
ruling that promised this change would close it.
134+
130135
## Configuration
131136

132137
Every environment key this brick reads is declared once in `src/flows_config.py`, with its class

core/flows/src/flows_steps/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
AGENT_API = flows_config.get("VEXA_FLOWS_AGENT_API_URL")
2121

2222
#: THE MEETINGS DOOR, and the name is a defect this change does not close. The key still says
23-
#: GATEWAY because flows reaches meetings THROUGH THE EDGE today — twelve call sites, all of them
23+
#: GATEWAY because flows reaches meetings THROUGH THE EDGE today — eleven call sites, all of them
2424
#: `{gateway}/meetings`, `{gateway}/bots`, `{gateway}/transcripts/…`. ADR-0037 forbids that hop
2525
#: ("fronting a sibling's door with the edge does not make it not-an-edge") and it is a separate
2626
#: change with its own consequences, measured and stated on this PR: the gateway resolves the

core/flows/tests/test_no_meetings.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
from __future__ import annotations
2525

2626
import ast
27+
import os
2728
import pathlib
29+
import subprocess
30+
import sys
2831

2932
import flows_config
3033
import pytest
@@ -60,10 +63,18 @@ def test_the_meetings_door_is_a_capability_not_a_required_one():
6063
assert cls == "capability" and default is None
6164

6265

63-
def test_an_unnamed_meetings_door_does_not_stop_the_process_booting():
66+
def test_an_unnamed_meetings_door_does_not_stop_the_process_booting(monkeypatch):
6467
"""THE FIRST DEFECT. `missing_doors()` filters on the class, so the reclass is the whole fix —
65-
but it is the half nothing else would notice, because every deployment in the tree names it."""
68+
but it is the half nothing else would notice, because every deployment in the tree names it.
69+
70+
THE DOOR IS GENUINELY UNSET HERE, and that is the whole test. `conftest.OFFLINE_DOORS` declares
71+
`VEXA_FLOWS_GATEWAY_URL` for the entire suite, so an assertion made without this `delenv` reads
72+
`missing_doors()` on a process that names the door — and returns [] for it whatever its class.
73+
It would have passed against the tree this change fixes: a test that cannot fail is the
74+
success-shaped failure the reclass exists to remove."""
75+
monkeypatch.delenv(common.MEETINGS_DOOR, raising=False)
6676
assert common.MEETINGS_DOOR not in flows_config.missing_doors()
77+
flows_config.preflight() # and the boot itself does not refuse
6778

6879

6980
@pytest.mark.parametrize("value,present", [("", False), (" ", False), ("http://gw:8000", True)])
@@ -103,12 +114,33 @@ def test_the_step_module_binds_no_door_at_import():
103114
assert "meetings_door" in imported, "the door must be resolved at ACCESS, per call"
104115

105116

117+
def test_the_step_vocabulary_imports_in_a_process_that_names_no_meetings_door():
118+
"""THE SAME DEFECT, PROVED RATHER THAN READ. The assertion above is a source scan, because an
119+
import that already succeeded inside this process cannot be asked whether it would have. So ask
120+
a process that genuinely has no door: a fresh interpreter with `VEXA_FLOWS_GATEWAY_URL` removed
121+
from the environment, importing the whole production step vocabulary.
122+
123+
Against the tree before this change it exits non-zero with a ConfigError raised THROUGH an
124+
import statement — the shape that made an optional domain impossible."""
125+
env = {k: v for k, v in os.environ.items() if k != common.MEETINGS_DOOR}
126+
env["PYTHONPATH"] = str(SRC)
127+
r = subprocess.run(
128+
[sys.executable, "-c",
129+
"import os, flows_defs.production as p, flows_steps.common as c;"
130+
"assert not os.environ.get('VEXA_FLOWS_GATEWAY_URL'), 'the door leaked into the child';"
131+
"assert c.domain_present('meetings') is False;"
132+
"assert c.domain_present('identity') is True;"
133+
"print('imported', len(dir(p)))"],
134+
env=env, capture_output=True, text=True, timeout=120)
135+
assert r.returncode == 0, f"the step vocabulary would not import with no meetings door:\n{r.stderr}"
136+
137+
106138
def test_every_meetings_url_is_built_from_the_access_time_door():
107139
"""The net under the assertion above: a site that went back to a module constant would import
108140
cleanly and fail at the first call in a deployment nobody tests."""
109141
text = (SRC / "flows_steps" / "meeting.py").read_text()
110142
assert "{GATEWAY}" not in text, "a call site still names the module-level door constant"
111-
assert text.count("meetings_door()") >= 11, "the twelve sites resolve the door per call"
143+
assert text.count("meetings_door()") == 11, "the eleven sites resolve the door per call"
112144

113145

114146
# ── the declarations on the steps ────────────────────────────────────────────────────────────
@@ -155,7 +187,11 @@ def _forbidden(*a, **k):
155187
monkeypatch.setattr(production.mt, attr, _forbidden)
156188
monkeypatch.setattr(common, "MEETINGS_API", "")
157189

158-
absent = lambda d: d != "meetings" # noqa: E731
190+
# THE REAL PREDICATE, over the door emptied above — not a lambda standing in for it. A
191+
# hand-written `lambda d: d != "meetings"` proves the ENGINE degrades and says nothing about
192+
# whether `domain_present` reads the configuration, which is the half that ships. Identity is
193+
# still present here, and so is the agent domain: exactly one door is missing.
194+
absent = common.domain_present
159195
terminal = {"done", "failed", "cancelled"}
160196
for (name, version), flow in reg.flows.items():
161197
db, clock = SqliteDB(), FakeClock()

scripts/domain-doors.allow.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,31 +125,31 @@
125125
{
126126
"path": "core/flows/src/flows_steps/common.py",
127127
"door": "VEXA_FLOWS_GATEWAY_URL",
128-
"line": 88,
128+
"line": 34,
129129
"from": "flows",
130130
"to": "gateway",
131-
"ruling": "PRD decision 40.7 and decision 5 (flows → meetings, 12 call sites via the gateway; architecture pass §1). To be declared an OPTIONAL meetings dependency — capability class + degrade — in a separate PR; that declaration removes this entry."
131+
"ruling": "PRD decision 40.7 and decision 5 (flows → meetings, 12 call sites via the gateway; architecture pass §1). THE CAPABILITY DECLARATION HAS LANDED and this entry did NOT close with it — the previous ruling promised it would, and the checker cannot honour that. VEXA_FLOWS_GATEWAY_URL is now class 'capability' with a declared degrade (flows_config.py, config.v1.json capability 'meetings'), and eight production steps answer meetings:not_present — but judge() refuses a DOMAIN naming an EDGE unconditionally, before it ever looks at the class: the capability escape hatch is a domain→domain rule, and doorOwner() resolves this key to the gateway. THE ENTRY CLOSES ON THE DE-HOP, not on a class. And the de-hop is not a rename: the gateway resolves the caller's key and enriches every forward with X-User-Id, X-User-Scopes, X-User-Workspaces and X-User-Limits, and meeting-api's POST /bots derives the per-user concurrent-bot cap from the last of those (core/meetings/services/meeting-api/src/meeting_api/bot_spawn/router.py:208 _resolve_max_concurrent). Calling meeting-api directly without carrying those forward drops the cap silently, so this entry closes only when that enrichment has a home."
132132
},
133133
{
134134
"path": "core/flows/src/flows_steps/common.py",
135135
"door": "UI_URL",
136-
"line": 90,
136+
"line": 139,
137137
"from": "flows",
138138
"to": "terminal",
139139
"ruling": "PRD decision 46 — the EDGE mints what a client sees. flows holds VEXA_UI_URL because every mailed link needs an origin; the entry closes when the origin is minted at the edge."
140140
},
141141
{
142142
"path": "core/flows/src/flows_steps/common.py",
143143
"door": "VEXA_UI_URL",
144-
"line": 204,
144+
"line": 253,
145145
"from": "flows",
146146
"to": "terminal",
147147
"ruling": "PRD decision 46 — the EDGE mints what a client sees. flows holds VEXA_UI_URL because every mailed link needs an origin; the entry closes when the origin is minted at the edge."
148148
},
149149
{
150150
"path": "core/gateway/services/gateway/src/gateway/adapters.py",
151151
"door": "AGENT_API_URL",
152-
"line": 204,
152+
"line": 209,
153153
"from": "gateway",
154154
"to": "agent",
155155
"ruling": "PRD decision 40.5 / 46 — the edge assembles routes.v1 and mcp.tools.v1 FROM THE DOMAINS PRESENT. agent exports no manifest yet; the entry closes when core/agent ships one declaring base_url_env AGENT_API_URL."

0 commit comments

Comments
 (0)