From 4514a9ef2011e5d4a8fdf47fcd88807dcb7cb53f Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:22:38 +0000 Subject: [PATCH 1/5] T078: 16.1, the OpenAI-compatible dialect joins DIALECTS (plan 034) `openai-chat-v1` joins doxbench_binding.DIALECTS as the second member, after `xfactory-prompt-v1`, which stays first. Its request is the chat-completions grammar (`model`, `messages`, the assembled prompt as one message in the user role), and its answer is read at `choices[0].message.content`. Both are spoken by one arm in doxbench_provider.py alone (`_DIALECT_ARMS`), beside the prompt grammar's arm, which sends the bytes it always sent. An unknown dialect is still refused when a binding is declared, and a test holds the arm table's keys equal to the vocabulary. Falsifier: F16.1's dialect assertion, `"openai-chat-v1" in b.DIALECTS`. Ruled: R1Q22 (a), openxFactory#656 comment 5817152735. doxbench_binding.py is a moved_verbatim row, and editing it needs no declared-edit act. Drafted ahead of T063 under Brett's phase-3 word ("Only the independent ones"). It does not land before T063. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) --- src/opendox/doxbench_binding.py | 30 ++-- src/opendox/doxbench_provider.py | 106 ++++++++++-- tests/test_model_provider_broker.py | 249 +++++++++++++++++++++++++++- 3 files changed, 358 insertions(+), 27 deletions(-) diff --git a/src/opendox/doxbench_binding.py b/src/opendox/doxbench_binding.py index fdd9e2ff..3b1e2131 100644 --- a/src/opendox/doxbench_binding.py +++ b/src/opendox/doxbench_binding.py @@ -93,21 +93,31 @@ AUTH_KINDS: tuple[str, ...] = (AUTH_KIND_API_KEY, AUTH_KIND_OAUTH) #: The CLOSED dialect vocabulary a binding may declare — the request grammar the -#: provider client speaks at the declared endpoint. ONE member today: this -#: repository's own already-declared turn shape, a prompt in and an -#: `assistant_prose` out, which is the shape `doxbench_model.dispatch_turn` -#: validates on the way back, so no second response grammar exists to keep -#: honest. CLOSED rather than open because an UNKNOWN dialect must REFUSE rather -#: than be guessed at: sending an assembled prompt to an endpoint whose grammar -#: this client does not know is a paid call that cannot succeed. A second member -#: joins here and an arm joins beside the first in `doxbench_provider`; the check -#: is never loosened. +#: provider client speaks at the declared endpoint. CLOSED rather than open +#: because an UNKNOWN dialect must REFUSE rather than be guessed at: sending an +#: assembled prompt to an endpoint whose grammar this client does not know is a +#: paid call that cannot succeed. A member joins here and an arm joins beside the +#: others in `doxbench_provider`; the check is never loosened. +#: +#: TWO MEMBERS, and the second joined exactly that way (#1144 box 16.1; plan 034 +#: T078): +#: +#: * `xfactory-prompt-v1` — this repository's own already-declared turn shape, +#: a POST of a model and a prompt answered by an `assistant_prose`, which is +#: the shape `doxbench_model.dispatch_turn` validates on the way back. It +#: stays FIRST, and it is unchanged byte for byte; +#: * `openai-chat-v1` — the OpenAI-compatible chat-completions grammar: a +#: request of a model and a list of messages, answered by the content of +#: the first choice's message. It is what a hosted API and the usual local +#: server both speak, which the first member does not. Its arm is in +#: `doxbench_provider` alone, beside the first one's. #: #: THE VOCABULARY LIVES HERE, on the record that declares it, and #: `doxbench_provider` reads it from this module — so an unknown dialect is #: refused when an operator DECLARES the binding rather than when a turn fails. DIALECT_XFACTORY_PROMPT_V1 = "xfactory-prompt-v1" -DIALECTS: tuple[str, ...] = (DIALECT_XFACTORY_PROMPT_V1,) +DIALECT_OPENAI_CHAT_V1 = "openai-chat-v1" +DIALECTS: tuple[str, ...] = (DIALECT_XFACTORY_PROMPT_V1, DIALECT_OPENAI_CHAT_V1) #: The URL schemes a declared endpoint may carry. `http://` is permitted for the #: on-this-host proxy posture an operator may legitimately run; a scheme this diff --git a/src/opendox/doxbench_provider.py b/src/opendox/doxbench_provider.py index fcff458b..42e9ad9e 100644 --- a/src/opendox/doxbench_provider.py +++ b/src/opendox/doxbench_provider.py @@ -34,7 +34,10 @@ console process. Brett's ruling of 2026-08-08: the broker mints, doxBench calls, because a broker in the request path adds a hop to every turn and to every chunk of a streamed one. WHERE to call and WHAT GRAMMAR to speak are - the BINDING's — the broker's declaration emits neither, deliberately; + the BINDING's — the broker's declaration emits neither, deliberately. Each + grammar the binding may declare has one arm here (`_DIALECT_ARMS`): this + repository's own prompt grammar, and the OpenAI-compatible chat-completions + grammar (#1144 box 16.1); * EXPIRY is handled by the 2026-08-26 ruling: re-mint and retry ONCE, with the re-mint and the paid retry visibly recorded, and a second expiry inside one turn surfaces the standard refusal rather than buying a third call. The @@ -175,8 +178,9 @@ #: it is the record that validates it). Aliased rather than respelled so the two #: modules cannot drift into two vocabularies. An unknown dialect is refused #: when an operator DECLARES the binding — earlier than a mint, and earlier than -#: a paid call. +#: a paid call. Each member has exactly one ARM below (`_DIALECT_ARMS`). DIALECT_XFACTORY_PROMPT_V1 = binding_mod.DIALECT_XFACTORY_PROMPT_V1 +DIALECT_OPENAI_CHAT_V1 = binding_mod.DIALECT_OPENAI_CHAT_V1 DIALECTS: tuple[str, ...] = binding_mod.DIALECTS #: How long a broker invocation may take. A mint is a local process doing local @@ -616,13 +620,27 @@ def list_references(binding, *, runner=subprocess_broker_runner) -> list: # the provider transport # --------------------------------------------------------------------------- -#: The provider request's own field names, in the ONE dialect this client -#: speaks. Named constants rather than inline literals so the boundary test can -#: assert they exist only here. +#: The provider request's own field names, per dialect. Named constants rather +#: than inline literals so the boundary test can assert they exist only here. +#: `model` is the one field both grammars share. PROVIDER_REQUEST_MODEL_FIELD = "model" + +#: `xfactory-prompt-v1`: a model and a prompt in, an `assistant_prose` out. PROVIDER_REQUEST_PROMPT_FIELD = "prompt" PROVIDER_RESPONSE_PROSE_FIELD = "assistant_prose" +#: `openai-chat-v1` (#1144 box 16.1; plan 034 T078): the chat-completions +#: request, a model and a list of messages, and its answer, the content of the +#: first choice's message. The assembled prompt travels as ONE message in the +#: user role. Prompt assembly is on the other side of the port (D14), so this +#: arm carries the text it was given and composes no message of its own. +PROVIDER_REQUEST_MESSAGES_FIELD = "messages" +CHAT_MESSAGE_ROLE_FIELD = "role" +CHAT_MESSAGE_CONTENT_FIELD = "content" +CHAT_ROLE_USER = "user" +CHAT_RESPONSE_CHOICES_FIELD = "choices" +CHAT_RESPONSE_MESSAGE_FIELD = "message" + #: The status a provider returns when the presented token is no longer good. #: 401 only: a 403 is an authorization verdict about what the token may do, #: which re-minting the same scope cannot change, and retrying it would buy a @@ -630,6 +648,62 @@ def list_references(binding, *, runner=subprocess_broker_runner) -> list: PROVIDER_STATUS_TOKEN_EXPIRED = 401 +def _prompt_request(model: str, prompt: str) -> dict: + """`xfactory-prompt-v1`'s request, exactly as it has always been sent.""" + return {PROVIDER_REQUEST_MODEL_FIELD: model, + PROVIDER_REQUEST_PROMPT_FIELD: prompt} + + +def _prompt_answer(document: dict) -> str: + """`xfactory-prompt-v1`'s answer: its `assistant_prose`, a string.""" + prose = document.get(PROVIDER_RESPONSE_PROSE_FIELD) + if not isinstance(prose, str): + raise BrokerRefused(DIAG_PROVIDER_MALFORMED) + return prose + + +def _chat_request(model: str, prompt: str) -> dict: + """`openai-chat-v1`'s request: the model, and the prompt as one message in + the user role.""" + return {PROVIDER_REQUEST_MODEL_FIELD: model, + PROVIDER_REQUEST_MESSAGES_FIELD: [ + {CHAT_MESSAGE_ROLE_FIELD: CHAT_ROLE_USER, + CHAT_MESSAGE_CONTENT_FIELD: prompt}]} + + +def _chat_answer(document: dict) -> str: + """`openai-chat-v1`'s answer: `choices[0].message.content`, a string. + + Read at exactly that path and nowhere else. A body with no first choice, a + choice with no message, or a message whose content is not text (a tool-call + answer carries null there) is not an answer this seam can hand back as + prose. Each lands on the fixed `DIAG_PROVIDER_MALFORMED` that every other + unusable answer lands on. Nothing past the first choice is read: the + request asks for one.""" + choices = document.get(CHAT_RESPONSE_CHOICES_FIELD) + if not isinstance(choices, list) or not choices: + raise BrokerRefused(DIAG_PROVIDER_MALFORMED) + first = choices[0] + message = (first.get(CHAT_RESPONSE_MESSAGE_FIELD) + if isinstance(first, dict) else None) + content = (message.get(CHAT_MESSAGE_CONTENT_FIELD) + if isinstance(message, dict) else None) + if not isinstance(content, str): + raise BrokerRefused(DIAG_PROVIDER_MALFORMED) + return content + + +#: ONE ARM PER DECLARED DIALECT: the function that builds its request and the +#: function that reads its answer. The record's closed vocabulary +#: (`doxbench_binding.DIALECTS`) refuses any other member at declaration, and a +#: test holds this table's keys equal to that vocabulary, so a member cannot +#: join one without the other. +_DIALECT_ARMS: dict[str, tuple] = { + DIALECT_XFACTORY_PROMPT_V1: (_prompt_request, _prompt_answer), + DIALECT_OPENAI_CHAT_V1: (_chat_request, _chat_answer), +} + + def _post_to_provider(token: MintedToken, *, model_id: str, prompt: str, timeout: float, opener) -> str: """The ONE place a provider is contacted. Returns the assistant prose. @@ -638,6 +712,11 @@ def _post_to_provider(token: MintedToken, *, model_id: str, prompt: str, it is not in the URL (which a proxy logs), not in the body (which an error handler might echo), and not in this function's return value. + THE GRAMMAR IS THE BINDING'S DIALECT (#1144 box 16.1), which the token + carries from the binding. Its arm in `_DIALECT_ARMS` builds the request + body and reads the answer. The route, the header, the bound, the expiry + status and every refusal below are the same for both dialects. + THE ANSWER IS BOUNDED (PR #392 review note b). `response.read()` with no argument reads until the peer stops sending, which makes the memory of this process a function of what a declared endpoint chooses to send — and the @@ -645,10 +724,14 @@ def _post_to_provider(token: MintedToken, *, model_id: str, prompt: str, byte over `MAX_PROVIDER_ANSWER_BYTES` is read deliberately, so an answer that is exactly at the bound is still honoured while one past it is detected rather than truncated into a shorter document that would parse.""" - body = json.dumps({ - PROVIDER_REQUEST_MODEL_FIELD: model_id, - PROVIDER_REQUEST_PROMPT_FIELD: prompt, - }).encode("utf-8") + arm = _DIALECT_ARMS.get(token.dialect) + if arm is None: + raise AssertionError( + f"{token.dialect!r} is outside the declared dialect vocabulary " + f"{DIALECTS}; the binding refuses it at declaration, so no turn " + "can carry one") + build_request, read_answer = arm + body = json.dumps(build_request(model_id, prompt)).encode("utf-8") request = urllib.request.Request( # noqa: S310 - endpoint declared on the binding by its operator, carried on the minted token token.endpoint, data=body, method="POST") request.add_header("Content-Type", "application/json") @@ -680,10 +763,7 @@ def _post_to_provider(token: MintedToken, *, model_id: str, prompt: str, raise BrokerRefused(DIAG_PROVIDER_MALFORMED) from error if not isinstance(document, dict): raise BrokerRefused(DIAG_PROVIDER_MALFORMED) - prose = document.get(PROVIDER_RESPONSE_PROSE_FIELD) - if not isinstance(prose, str): - raise BrokerRefused(DIAG_PROVIDER_MALFORMED) - return prose + return read_answer(document) # --------------------------------------------------------------------------- diff --git a/tests/test_model_provider_broker.py b/tests/test_model_provider_broker.py index 4a5fbaea..9fe6099e 100644 --- a/tests/test_model_provider_broker.py +++ b/tests/test_model_provider_broker.py @@ -21,6 +21,9 @@ redacted refusal `doxbench_model.dispatch_turn` already defines, and the UNCONFIGURED posture is byte-for-byte what it was before this change. +A SIXTH LAYER, (f), holds #1144 Group 16's binding and provider boxes (plan +034 phase 3, slice P3-B). 16.1 is the OpenAI-compatible dialect (T078). + THE FAKE BROKER SPEAKS THE DECLARED CONTRACT (task 2.6). It was this repository's own invented stdin/stdout protocol until the reconciliation, which meant every test here agreed with a broker that does not exist. It now takes the @@ -40,6 +43,7 @@ from __future__ import annotations +import contextlib import dataclasses import http.server import io @@ -145,8 +149,12 @@ def test_the_dialect_vocabulary_is_closed_and_refuses_at_declaration(): there; openProfiler's declaration emits no dialect at all, so the fact is the BINDING's and the refusal happens when an operator DECLARES one — before any broker is invoked and long before a paid call. Closed, still: an unknown - grammar refuses rather than being guessed at.""" - assert binding_mod.DIALECTS == ("xfactory-prompt-v1",) + grammar refuses rather than being guessed at. + + TWO MEMBERS since #1144 box 16.1 (plan 034 T078), and the order is pinned: + the prompt grammar stays first, and the OpenAI-compatible chat grammar + joins after it. The refusal below is the same refusal it always was.""" + assert binding_mod.DIALECTS == ("xfactory-prompt-v1", "openai-chat-v1") assert provider_mod.DIALECTS is binding_mod.DIALECTS, \ "one vocabulary, read from the record that declares it" with pytest.raises(binding_mod.BindingRefused) as caught: @@ -866,9 +874,9 @@ def _expired_error(): def _port(tmp_path, *outcomes, expires=None, notice=None, clock=time.time, - endpoint=ENDPOINT): + endpoint=ENDPOINT, dialect=binding_mod.DIALECT_XFACTORY_PROMPT_V1): script = _write_broker(tmp_path, expires=expires) - binding = _broker_binding(script, endpoint=endpoint) + binding = _broker_binding(script, endpoint=endpoint, dialect=dialect) opener = _Opener(*outcomes) port = provider_mod.BrokeredProviderPort( binding, install_mod.brokered_catalog(binding), @@ -1336,3 +1344,236 @@ def test_the_subprocess_runner_never_uses_a_shell(tmp_path): assert "shell=True" not in source assert "os.system" not in source assert subprocess.Popen is subprocess.Popen # the module spawns, nothing else + + +# =========================================================================== +# (f) CHAT'S MODEL CONFIGURATION (#1144 Group 16; plan 034 phase 3, P3-B) +# =========================================================================== +# +# 16.1, the OpenAI-compatible dialect (T078). `openai-chat-v1` is the second +# `DIALECTS` member. Its request is the chat-completions grammar (`model`, +# `messages`), and its answer is read at `choices[0].message.content`. Both are +# spoken by one arm in `doxbench_provider`, beside the prompt grammar's arm. + +OPENAI_CHAT = binding_mod.DIALECT_OPENAI_CHAT_V1 + + +def _chat_completion(content="the chat answer"): + """A chat-completions answer in that grammar's own shape. The keys around + `choices` are what a real server sends, and nothing here reads them.""" + return {"id": "chatcmpl-stand-in", "object": "chat.completion", + "model": "stand-in-model", + "choices": [{"index": 0, "finish_reason": "stop", + "message": {"role": "assistant", + "content": content}}]} + + +def test_f16_1_the_openai_compatible_dialect_is_declared(): + """F16.1's dialect assertion, as #1144 writes it: + `assert "openai-chat-v1" in b.DIALECTS`. A binding may declare it.""" + assert "openai-chat-v1" in binding_mod.DIALECTS, ( + f"no OpenAI-compatible dialect: {binding_mod.DIALECTS}") + assert OPENAI_CHAT == "openai-chat-v1" + assert _binding(dialect=OPENAI_CHAT).dialect == OPENAI_CHAT + assert provider_mod.DIALECT_OPENAI_CHAT_V1 is OPENAI_CHAT, \ + "one spelling, read from the record that declares it" + + +def test_every_declared_dialect_has_exactly_one_arm_in_the_provider_module(): + """A member cannot join the vocabulary without an arm, or an arm exist + for a member the record would refuse.""" + assert set(provider_mod._DIALECT_ARMS) == set(binding_mod.DIALECTS) + + +def test_a_chat_turn_speaks_the_chat_completions_grammar(tmp_path): + port, opener = _port(tmp_path, _chat_completion("the answer"), + dialect=OPENAI_CHAT) + assert port.dispatch(_Envelope()) == {"assistant_prose": "the answer", + "proposals": []} + request = opener.requests[0] + assert request.get_method() == "POST" + assert request.get_full_url() == ENDPOINT + assert json.loads(request.data.decode("utf-8")) == { + "model": "openprofiler-demo", + "messages": [{"role": "user", "content": "assembled prompt"}]} + assert request.get_header("Content-type") == "application/json" + # the token travels in the header, exactly as it does for the prompt grammar + assert request.get_header("Authorization") == f"Bearer {SENTINEL_TOKEN}" + assert SENTINEL_TOKEN not in request.get_full_url() + assert SENTINEL_TOKEN not in request.data.decode("utf-8") + + +def test_the_prompt_dialect_is_unchanged_byte_for_byte(tmp_path): + """The first member's request is the bytes it always was: the arm table + moved the code, and nothing it sends.""" + port, opener = _port(tmp_path, {"assistant_prose": "a"}) + assert port.dispatch(_Envelope())["assistant_prose"] == "a" + assert opener.requests[0].data == json.dumps( + {"model": "openprofiler-demo", "prompt": "assembled prompt"} + ).encode("utf-8") + + +@pytest.mark.parametrize("answer", [ + {}, + {"choices": []}, + {"choices": "not a list"}, + {"choices": ["not an object"]}, + {"choices": [{}]}, + {"choices": [{"message": "not an object"}]}, + {"choices": [{"message": {"role": "assistant"}}]}, + {"choices": [{"message": {"role": "assistant", "content": None}}]}, + {"choices": [{"message": {"role": "assistant", "content": 7}}]}, + {"assistant_prose": "the prompt grammar's answer, not this one's"}, +], ids=["empty", "no-choice", "choices-not-a-list", "choice-not-an-object", + "no-message", "message-not-an-object", "no-content", "null-content", + "content-not-text", "the-other-grammar"]) +def test_a_chat_answer_off_the_declared_path_is_malformed(tmp_path, answer): + port, _opener = _port(tmp_path, answer, dialect=OPENAI_CHAT) + with pytest.raises(provider_mod.BrokerRefused) as caught: + port.dispatch(_Envelope()) + assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_MALFORMED + + +def test_a_chat_shaped_answer_is_not_the_prompt_grammars_answer(tmp_path): + """Each arm reads its own grammar and no other.""" + port, _opener = _port(tmp_path, _chat_completion()) + with pytest.raises(provider_mod.BrokerRefused) as caught: + port.dispatch(_Envelope()) + assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_MALFORMED + + +def test_only_the_first_choice_is_read(tmp_path): + answer = _chat_completion("first") + answer["choices"].append({"index": 1, "finish_reason": "stop", + "message": {"role": "assistant", + "content": "second"}}) + port, _opener = _port(tmp_path, answer, dialect=OPENAI_CHAT) + assert port.dispatch(_Envelope())["assistant_prose"] == "first" + + +def test_the_expiry_ruling_holds_for_the_chat_grammar(tmp_path): + """The 2026-08-26 ruling is the port's, not a dialect's: a mid-turn expiry + re-mints and retries once, visibly, in either grammar.""" + printed: list[str] = [] + port, opener = _port(tmp_path, _expired_error(), + _chat_completion("the retried answer"), + notice=printed.append, dialect=OPENAI_CHAT) + assert port.dispatch(_Envelope())["assistant_prose"] == "the retried answer" + assert len(opener.requests) == 2, "exactly one paid retry" + assert [event.reason for event in port.ledger] == [ + provider_mod.REASON_FIRST_MINT, + provider_mod.REASON_EXPIRY_REMINT, + provider_mod.REASON_PAID_RETRY, + ] + assert printed and "re-minted once and retried" in printed[0] + + +def test_the_answer_bound_holds_for_the_chat_grammar(tmp_path): + bound = provider_mod.MAX_PROVIDER_ANSWER_BYTES + oversize = json.dumps(_chat_completion("x" * bound)).encode("utf-8") + port, _opener = _port(tmp_path, oversize, dialect=OPENAI_CHAT) + with pytest.raises(provider_mod.BrokerRefused) as caught: + port.dispatch(_Envelope()) + assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_MALFORMED + + +def test_a_chat_provider_refusal_lands_on_the_fixed_sentence(tmp_path): + port, _opener = _port( + tmp_path, + urllib.error.HTTPError(ENDPOINT, 400, "Bad Request", {}, + io.BytesIO(b'{"error":{"message":"leaky"}}')), + dialect=OPENAI_CHAT) + with pytest.raises(provider_mod.BrokerRefused) as caught: + port.dispatch(_Envelope()) + assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_REFUSED + assert "leaky" not in str(caught.value) + + +@contextlib.contextmanager +def _stand_in_provider(handler_class): + """A stand-in provider on loopback for the length of one test. It yields + the server's base URL, and it is shut down and joined however the test + ends.""" + server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler_class) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + host, prt = server.server_address[:2] + yield f"http://{host}:{prt}" + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + +def _answer_json(handler, document) -> None: + """Answer one stand-in request with `document` as a JSON body.""" + payload = json.dumps(document).encode("utf-8") + handler.send_response(200) + handler.send_header("Content-Type", "application/json") + handler.send_header("Content-Length", str(len(payload))) + handler.end_headers() + handler.wfile.write(payload) + + +class _ChatCompletionsHandler(http.server.BaseHTTPRequestHandler): + """A stand-in OpenAI-compatible server on loopback. It records each + request and answers in the chat-completions grammar.""" + + seen: dict = {} + + def do_POST(self): # noqa: N802 - BaseHTTPRequestHandler's own spelling + length = int(self.headers.get("Content-Length", "0")) + _ChatCompletionsHandler.seen = { + "path": self.path, + "authorization": self.headers.get("Authorization"), + "content_type": self.headers.get("Content-Type"), + "body": json.loads(self.rfile.read(length).decode("utf-8")), + } + _answer_json(self, _chat_completion("answered in the chat grammar")) + + def log_message(self, *_args): + return + + +def test_a_chat_turn_reaches_a_stand_in_chat_completions_server(tmp_path): + """The real `urllib` path, in the chat grammar: a stand-in server on + loopback receives the request at the binding's declared endpoint, in that + grammar, with the token in the authorization header and nowhere else.""" + with _stand_in_provider(_ChatCompletionsHandler) as base: + binding = _broker_binding(_write_broker(tmp_path), + endpoint=f"{base}/v1/chat/completions", + dialect=OPENAI_CHAT) + port = provider_mod.BrokeredProviderPort( + binding, install_mod.brokered_catalog(binding), + notice=lambda _text: None) + assert port.dispatch(_Envelope()) == { + "assistant_prose": "answered in the chat grammar", + "proposals": []} + + seen = _ChatCompletionsHandler.seen + assert seen["path"] == "/v1/chat/completions" + assert seen["authorization"] == f"Bearer {SENTINEL_TOKEN}" + assert seen["content_type"] == "application/json" + assert seen["body"] == { + "model": "openprofiler-demo", + "messages": [{"role": "user", "content": "assembled prompt"}]} + assert SENTINEL_TOKEN not in json.dumps(seen["body"]) + + +def test_the_cli_declares_a_chat_binding(tmp_path, capsys): + """The operator door offers the dialect, because its choices are read from + the record's vocabulary rather than respelled.""" + checkout = tmp_path / "checkout" + checkout.mkdir() + args = cli_mod.build_parser().parse_args([ + "model-binding", "add", "--repo-root", str(checkout), + "--id", "local-chat", "--label", "Local chat", "--provider", "local", + "--credential-ref", FAKE_REFERENCE, "--auth-kind", "api_key", + "--credential-approver", "brett@opensoft.one", + "--endpoint", "http://127.0.0.1:9/v1/chat/completions", + "--dialect", OPENAI_CHAT, "--", "openprofiler-broker"]) + assert args.func(args) == 0 + capsys.readouterr() + store = binding_mod.BindingStore(binding_mod.bindings_path(checkout)) + assert store.get("local-chat").dialect == OPENAI_CHAT From 7c83c2cd0bbabc8a6005ee524a8ed018f9c9c807 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:29:46 +0000 Subject: [PATCH 2/5] T079: 16.2, a `model` field the provider receives (plan 034) The binding record gains `model`, the model name the provider receives as the request's model, in either dialect. It sits after `dialect`, with the route it belongs to, so BINDING_FIELDS grows from nine to ten, and still no field can hold a secret. `model-binding add|edit --model` sets it, and `model-binding list` shows it. A binding that declares no model keeps the meaning it had: the request names the catalog handle, the binding's `id`, byte for byte. So `model` is keyword-only and defaults to None, and a stored record may leave it out (OPTIONAL_BINDING_FIELDS). Every construction written before the field existed builds the binding it built, and every nine-field document reads. The console's intake route in serve_workbench.py builds a binding without a model, and it keeps working unedited. The catalog handle stays the binding's id, and `model` is not an argv placeholder. Falsifier: F16.1's field assertion, `"model" in b.BINDING_FIELDS`. Ruled: R1Q22 (a), openxFactory#656 comment 5817152735. Drafted ahead of T063 under Brett's phase-3 word ("Only the independent ones"). It does not land before T063. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) --- src/opendox/cli_model_binding.py | 21 ++- src/opendox/doxbench_binding.py | 47 +++++-- src/opendox/doxbench_provider.py | 26 ++-- tests/test_model_provider_broker.py | 208 +++++++++++++++++++++++++++- 4 files changed, 280 insertions(+), 22 deletions(-) diff --git a/src/opendox/cli_model_binding.py b/src/opendox/cli_model_binding.py index 2331034d..f5c15ad9 100644 --- a/src/opendox/cli_model_binding.py +++ b/src/opendox/cli_model_binding.py @@ -50,7 +50,14 @@ def _declared_binding(args: argparse.Namespace) -> "binding_mod.ModelProviderBin id=args.id, label=args.label, provider=args.provider, credential_ref=args.credential_ref, auth_kind=args.auth_kind, approved_by=args.approved_by, endpoint=args.endpoint, - dialect=args.dialect, broker_argv=tuple(args.broker_argv)) + dialect=args.dialect, model=args.model, + broker_argv=tuple(args.broker_argv)) + + +#: What `list` prints for a binding that declares no model (#1144 box 16.2). +#: The request then names the binding's id, as every request did before the +#: field existed, and the operator reading the list should see that. +NO_MODEL_DECLARED = "(none declared: the request names this binding's id)" def cmd_model_binding_list(args: argparse.Namespace) -> int: @@ -77,6 +84,9 @@ def cmd_model_binding_list(args: argparse.Namespace) -> int: print(f" credential ref {record['credential_ref']}") print(f" endpoint {record['endpoint']}") print(f" dialect {record['dialect']}") + model = record["model"] + print(f" model " + f"{model if model is not None else NO_MODEL_DECLARED}") print(f" broker argv {record['broker_argv']}") print(f" custody {record['credential_custody']}") return 0 @@ -203,6 +213,15 @@ def _add_binding_declaration_args(parser: argparse.ArgumentParser) -> None: parser.add_argument("--dialect", required=True, choices=list(binding_mod.DIALECTS), help="the request grammar that endpoint speaks") + # THE MODEL THE PROVIDER RECEIVES (#1144 box 16.2), the route's third fact. + # OPTIONAL, and that keeps a binding declared without it meaning what it + # always meant: the request names the binding's id. `edit` replaces the + # whole binding, as it always has, so an edit that omits `--model` declares + # none. + parser.add_argument("--model", default=None, + help="the model name the provider receives in each " + "request (default: none declared, and the " + "request names this binding's id)") # A POSITIONAL, taken after a bare `--`, and that is the fix for a real # trap rather than a style choice: a broker invocation is full of # option-shaped members (`--binding`, `--ref`), and as a flag's value they diff --git a/src/opendox/doxbench_binding.py b/src/opendox/doxbench_binding.py index 3b1e2131..c76ca89a 100644 --- a/src/opendox/doxbench_binding.py +++ b/src/opendox/doxbench_binding.py @@ -31,7 +31,9 @@ would then be accountable for. So provider routing is the CONSUMER's fact, and the consumer's declared record is where a fact the consumer owns belongs. Declaring a route is not holding a transport: nothing here opens a socket, and -the module still names no provider host of its own. +the module still names no provider host of its own. #1144 box 16.2 gave the +route a third fact, `model`: the model name the provider receives. It is the +consumer's fact for the same reason. THE BROKER INVOCATION IS DECLARED, NOT WRITTEN INTO CODE — the program and its fixed leading arguments. `broker_argv` is the BASE invocation and names no @@ -133,9 +135,14 @@ #: `provider` and `approved_by` are REQUIRED flags of the declared `intake` #: (`--provider`, `--approved-by`; the second because `credential-contracts` #: holds that a grant without an approver is invalid), and `endpoint`/`dialect` -#: are the provider route the mint answer deliberately does not carry. STILL NO -#: SECRET FIELD: nine fields, and the absence of a tenth is the same point the -#: absence of a sixth was. +#: are the provider route the mint answer deliberately does not carry. +#: +#: AND FROM NINE TO TEN BY #1144 box 16.2 (plan 034 T079): `model`, the model +#: name the provider receives as the request's model. It sits with the route +#: it belongs to, after `dialect`. Before it, the request named the catalog +#: handle, which is this binding's `id`, so no provider model could be named. +#: STILL NO SECRET FIELD: ten fields, and the absence of an eleventh is the +#: same point the absence of a sixth was. BINDING_FIELDS: tuple[str, ...] = ( "id", "label", @@ -145,16 +152,25 @@ "approved_by", "endpoint", "dialect", + "model", "broker_argv", ) +#: The one field a stored record may leave out. A record without `model` was +#: declared before the field existed, and it keeps the meaning it had: the +#: request names the catalog handle, this binding's `id`. Every other field is +#: required, as it always was. +OPTIONAL_BINDING_FIELDS: tuple[str, ...] = ("model",) + #: The CLOSED placeholder vocabulary an argv template may name. Every member is #: a field of the binding itself, which is the property that matters: a template #: can only ever be filled with facts the binding already discloses, so no #: substitution can smuggle a value the record does not carry. A template naming #: anything outside this set is refused at construction rather than at #: execution — an operator finds out when they declare the binding, not when a -#: turn fails. +#: turn fails. `model` is not a member: a broker's invocation is about custody, +#: never about which model a turn asks for, and an undeclared model has no +#: value to fill a placeholder with. ARGV_PLACEHOLDERS: tuple[str, ...] = ( "binding_id", "label", "provider", "credential_ref", "auth_kind", "approved_by", "endpoint", "dialect") @@ -195,12 +211,19 @@ def _require_non_blank_str(field: str, value: object) -> str: class ModelProviderBinding: """ONE model provider, as settings hold it. - Nine fields, and the absence of a tenth is the point (see the module + Ten fields, and the absence of an eleventh is the point (see the module docstring). `broker_argv` is the DECLARED BASE invocation as a tuple of argv members — argv, never a shell string, so no operator's label and no credential reference can ever be read as shell syntax. It names the program and its fixed leading arguments and NOT the operation: the operation is a declared subcommand `doxbench_provider` appends. + + `model` (#1144 box 16.2) is the model name the provider receives as the + request's model. It is KEYWORD-ONLY and defaults to None, so every + construction written before it existed still builds the binding it built. + That binding keeps its old meaning: with no model declared, the request + names the catalog handle, which is the binding's `id`, exactly as before. + A declared model is a non-blank string. """ id: str @@ -211,12 +234,15 @@ class ModelProviderBinding: approved_by: str endpoint: str dialect: str + model: str | None = dataclasses.field(default=None, kw_only=True) broker_argv: tuple[str, ...] def __post_init__(self) -> None: for field in ("id", "label", "provider", "credential_ref", "auth_kind", "approved_by", "endpoint", "dialect"): _require_non_blank_str(field, getattr(self, field)) + if self.model is not None: + _require_non_blank_str("model", self.model) if self.auth_kind not in AUTH_KINDS: raise BindingRefused( f"auth_kind {self.auth_kind!r} is outside the closed " @@ -259,7 +285,8 @@ def __post_init__(self) -> None: def as_record(self) -> dict: """The STORED record: the record kind, then exactly ``BINDING_FIELDS`` in order. `broker_argv` becomes a list because that is what YAML round - trips; nothing else changes shape.""" + trips. An undeclared `model` is written as null, so every stored + record carries all ten keys; nothing else changes shape.""" return { "kind": BINDING_KIND, "id": self.id, @@ -270,6 +297,7 @@ def as_record(self) -> dict: "approved_by": self.approved_by, "endpoint": self.endpoint, "dialect": self.dialect, + "model": self.model, "broker_argv": list(self.broker_argv), } @@ -334,7 +362,9 @@ def from_record(cls, record: object) -> "ModelProviderBinding": raise BindingRefused( f"a binding record declares kind {declared_kind!r}, not " f"{BINDING_KIND!r}") - missing = [field for field in BINDING_FIELDS if field not in record] + missing = [field for field in BINDING_FIELDS + if field not in record + and field not in OPTIONAL_BINDING_FIELDS] if missing: raise BindingRefused( f"a binding record is missing {missing}") @@ -347,6 +377,7 @@ def from_record(cls, record: object) -> "ModelProviderBinding": approved_by=record["approved_by"], endpoint=record["endpoint"], dialect=record["dialect"], + model=record.get("model"), broker_argv=record["broker_argv"], ) diff --git a/src/opendox/doxbench_provider.py b/src/opendox/doxbench_provider.py index 42e9ad9e..9317a995 100644 --- a/src/opendox/doxbench_provider.py +++ b/src/opendox/doxbench_provider.py @@ -704,10 +704,14 @@ def _chat_answer(document: dict) -> str: } -def _post_to_provider(token: MintedToken, *, model_id: str, prompt: str, +def _post_to_provider(token: MintedToken, *, model: str, prompt: str, timeout: float, opener) -> str: """The ONE place a provider is contacted. Returns the assistant prose. + `model` is the model name the request carries, which the port chose (the + binding's declared `model`, or the catalog handle for a binding that + declares none). + The token travels in the request's authorization header and nowhere else; it is not in the URL (which a proxy logs), not in the body (which an error handler might echo), and not in this function's return value. @@ -731,7 +735,7 @@ def _post_to_provider(token: MintedToken, *, model_id: str, prompt: str, f"{DIALECTS}; the binding refuses it at declaration, so no turn " "can carry one") build_request, read_answer = arm - body = json.dumps(build_request(model_id, prompt)).encode("utf-8") + body = json.dumps(build_request(model, prompt)).encode("utf-8") request = urllib.request.Request( # noqa: S310 - endpoint declared on the binding by its operator, carried on the minted token token.endpoint, data=body, method="POST") request.add_header("Content-Type", "application/json") @@ -909,15 +913,21 @@ def dispatch(self, prompt_envelope: object) -> object: unrelated issuances. The expired mint's reference is read off the token this turn is holding and lives no longer than the turn; * a SECOND expiry inside the same turn raises the standard refusal. - No third call is bought.""" - model_id = getattr(prompt_envelope, "model_id", None) - if not isinstance(model_id, str) or not model_id: + No third call is bought. + + THE REQUEST'S MODEL IS THE BINDING'S DECLARED `model` (#1144 box + 16.2). A binding that declares none sends the catalog handle, which is + what every request sent before the field existed, byte for byte.""" + handle = getattr(prompt_envelope, "model_id", None) + if not isinstance(handle, str) or not handle: entries = self._declared_catalog.entries - model_id = entries[0].model_id if entries else "" + handle = entries[0].model_id if entries else "" + declared_model = self._binding.model + model = declared_model if declared_model is not None else handle prompt = bridge_mod.render_prompt_message(prompt_envelope) token = self._current_token(REASON_FIRST_MINT) try: - prose = _post_to_provider(token, model_id=model_id, prompt=prompt, + prose = _post_to_provider(token, model=model, prompt=prompt, timeout=self._timeout_seconds, opener=self._opener) except _TokenExpired: @@ -932,7 +942,7 @@ def dispatch(self, prompt_envelope: object) -> object: self._record(REASON_PAID_RETRY) try: prose = _post_to_provider( - token, model_id=model_id, prompt=prompt, + token, model=model, prompt=prompt, timeout=self._timeout_seconds, opener=self._opener) except _TokenExpired: self._forget_token() diff --git a/tests/test_model_provider_broker.py b/tests/test_model_provider_broker.py index 9fe6099e..82bf9a11 100644 --- a/tests/test_model_provider_broker.py +++ b/tests/test_model_provider_broker.py @@ -22,7 +22,8 @@ UNCONFIGURED posture is byte-for-byte what it was before this change. A SIXTH LAYER, (f), holds #1144 Group 16's binding and provider boxes (plan -034 phase 3, slice P3-B). 16.1 is the OpenAI-compatible dialect (T078). +034 phase 3, slice P3-B). 16.1 is the OpenAI-compatible dialect (T078), and +16.2 is the model name the provider receives (T079). THE FAKE BROKER SPEAKS THE DECLARED CONTRACT (task 2.6). It was this repository's own invented stdin/stdout protocol until the reconciliation, which @@ -120,8 +121,9 @@ def test_the_binding_declares_exactly_the_fields_the_seam_needs(): "secret", "api_key", "token", "credential", "value", "password"]) def test_no_secret_field_exists_in_the_shape_to_populate(secret_field): """NOT OPTIONAL — ABSENT. The dataclass is slotted and frozen, so a secret - cannot be passed in and cannot be attached afterwards. Nine fields now - rather than five, and the absence of a tenth is the same claim.""" + cannot be passed in and cannot be attached afterwards. Ten fields now + rather than five (#1144 box 16.2 added `model`), and the absence of an + eleventh is the same claim.""" with pytest.raises(TypeError): _binding(**{secret_field: SENTINEL_CREDENTIAL}) binding = _binding() @@ -874,9 +876,11 @@ def _expired_error(): def _port(tmp_path, *outcomes, expires=None, notice=None, clock=time.time, - endpoint=ENDPOINT, dialect=binding_mod.DIALECT_XFACTORY_PROMPT_V1): + endpoint=ENDPOINT, dialect=binding_mod.DIALECT_XFACTORY_PROMPT_V1, + model=None): script = _write_broker(tmp_path, expires=expires) - binding = _broker_binding(script, endpoint=endpoint, dialect=dialect) + binding = _broker_binding(script, endpoint=endpoint, dialect=dialect, + model=model) opener = _Opener(*outcomes) port = provider_mod.BrokeredProviderPort( binding, install_mod.brokered_catalog(binding), @@ -1577,3 +1581,197 @@ def test_the_cli_declares_a_chat_binding(tmp_path, capsys): capsys.readouterr() store = binding_mod.BindingStore(binding_mod.bindings_path(checkout)) assert store.get("local-chat").dialect == OPENAI_CHAT + + +# 16.2, the model name the provider receives (T079). The record gains `model`, +# sent as the request's model and set by `model-binding add|edit --model`. The +# field list grows from nine to ten, and still no field can hold a secret. A +# binding that declares no model sends the catalog handle, its `id`, exactly as +# every request did before the field existed. + +DECLARED_MODEL = "stand-in-model-7b" + + +def test_f16_1_the_record_names_a_model(): + """F16.1's field assertion, as #1144 writes it: + `assert "model" in b.BINDING_FIELDS`. Ten fields, in their declared order, + with `model` beside the route it belongs to.""" + assert "model" in binding_mod.BINDING_FIELDS, ( + f"the record names no model: {binding_mod.BINDING_FIELDS}") + assert binding_mod.BINDING_FIELDS == ( + "id", "label", "provider", "credential_ref", "auth_kind", + "approved_by", "endpoint", "dialect", "model", "broker_argv") + assert binding_mod.OPTIONAL_BINDING_FIELDS == ("model",) + + +def test_the_model_is_keyword_only_and_undeclared_by_default(): + """Every construction written before the field existed builds the + binding it built, which declares no model.""" + import inspect + parameter = inspect.signature( + binding_mod.ModelProviderBinding).parameters["model"] + assert parameter.kind is inspect.Parameter.KEYWORD_ONLY + assert parameter.default is None + assert _binding().model is None + assert _binding(model=DECLARED_MODEL).model == DECLARED_MODEL + + +@pytest.mark.parametrize("bad", ["", " ", 7, ["a-model"]]) +def test_a_declared_model_is_non_blank_text(bad): + with pytest.raises(binding_mod.BindingRefused): + _binding(model=bad) + + +@pytest.mark.parametrize("dialect,answer,grammar_key", [ + (binding_mod.DIALECT_XFACTORY_PROMPT_V1, {"assistant_prose": "a"}, "prompt"), + (OPENAI_CHAT, _chat_completion("a"), "messages"), +]) +def test_the_declared_model_is_what_the_provider_receives(tmp_path, dialect, + answer, grammar_key): + port, opener = _port(tmp_path, answer, dialect=dialect, + model=DECLARED_MODEL) + assert port.dispatch(_Envelope())["assistant_prose"] == "a" + body = json.loads(opener.requests[0].data.decode("utf-8")) + assert body["model"] == DECLARED_MODEL + assert set(body) == {"model", grammar_key} + + +@pytest.mark.parametrize("dialect,answer", [ + (binding_mod.DIALECT_XFACTORY_PROMPT_V1, {"assistant_prose": "a"}), + (OPENAI_CHAT, _chat_completion("a")), +]) +def test_a_binding_with_no_model_still_sends_the_catalog_handle(tmp_path, + dialect, + answer): + """What every request sent before #1144 box 16.2, byte for byte.""" + port, opener = _port(tmp_path, answer, dialect=dialect) + port.dispatch(_Envelope()) + assert json.loads(opener.requests[0].data.decode("utf-8"))["model"] == \ + "openprofiler-demo" + + +def test_the_catalog_handle_stays_the_bindings_id(tmp_path): + """The model is what the PROVIDER receives. The menu's handle is still the + binding's id, so a chosen entry still resolves back to its binding.""" + binding = _binding(model=DECLARED_MODEL) + entries = install_mod.brokered_catalog(binding).entries + assert [entry.model_id for entry in entries] == [binding.id] + + +def test_a_declared_model_survives_the_expiry_retry(tmp_path): + port, opener = _port(tmp_path, _expired_error(), _chat_completion("b"), + dialect=OPENAI_CHAT, model=DECLARED_MODEL) + assert port.dispatch(_Envelope())["assistant_prose"] == "b" + assert [json.loads(request.data.decode("utf-8"))["model"] + for request in opener.requests] == [DECLARED_MODEL] * 2 + + +def test_the_model_is_not_an_argv_placeholder(): + """A broker's invocation is about custody, never about the model a turn + asks for, so the closed placeholder vocabulary does not grow.""" + assert "model" not in binding_mod.ARGV_PLACEHOLDERS + with pytest.raises(binding_mod.BindingRefused): + _binding(model=DECLARED_MODEL, + broker_argv=("openprofiler-broker", "--for", "{model}")) + + +def test_a_stored_record_carries_its_model_and_round_trips(tmp_path): + store = _store(tmp_path) + store.add(_binding(model=DECLARED_MODEL)) + store.add(_binding(id="undeclared", label="No model")) + import yaml + document = yaml.safe_load(store.path.read_text(encoding="utf-8")) + first, second = document["bindings"] + assert list(first) == ["kind", *binding_mod.BINDING_FIELDS] + assert first["model"] == DECLARED_MODEL + assert second["model"] is None, "an undeclared model is written as null" + assert store.get("openprofiler-demo").model == DECLARED_MODEL + assert store.get("undeclared").model is None + assert store.read_back()["bindings"][0]["model"] == DECLARED_MODEL + + +def test_a_record_declared_before_the_field_existed_still_reads(tmp_path): + """A nine-field record, as every stored document held until #1144 box + 16.2, reads as a binding that declares no model.""" + record = _binding().as_record() + del record["model"] + assert set(record) == {"kind", *binding_mod.BINDING_FIELDS} - {"model"} + path = tmp_path / "bindings.yaml" + path.write_text(json.dumps({"schema_version": 1, + "kind": binding_mod.BINDINGS_KIND, + "bindings": [record]}), encoding="utf-8") + (binding,) = binding_mod.BindingStore(path).list() + assert binding.model is None + assert binding == _binding() + + +def test_a_record_missing_a_required_field_still_refuses(): + """`model` is the one field a record may leave out, and only that one.""" + for field in binding_mod.BINDING_FIELDS: + if field in binding_mod.OPTIONAL_BINDING_FIELDS: + continue + record = _binding().as_record() + del record[field] + with pytest.raises(binding_mod.BindingRefused) as caught: + binding_mod.ModelProviderBinding.from_record(record) + assert field in str(caught.value) + + +def test_the_cli_sets_the_model_on_add_and_edit(tmp_path, capsys): + checkout = tmp_path / "checkout" + checkout.mkdir() + parser = cli_mod.build_parser() + + def run(*argv) -> int: + args = parser.parse_args(list(argv)) + return args.func(args) + + root = ["--repo-root", str(checkout)] + declaration = ["--id", "local-chat", "--label", "Local chat", + "--provider", "local", "--credential-ref", FAKE_REFERENCE, + "--auth-kind", "api_key", + "--credential-approver", "brett@opensoft.one", + "--endpoint", "http://127.0.0.1:9/v1/chat/completions", + "--dialect", OPENAI_CHAT] + store = binding_mod.BindingStore(binding_mod.bindings_path(checkout)) + + assert run("model-binding", "add", *root, *declaration, + "--model", DECLARED_MODEL, "--", "openprofiler-broker") == 0 + capsys.readouterr() + assert store.get("local-chat").model == DECLARED_MODEL + assert run("model-binding", "list", *root) == 0 + assert f"model {DECLARED_MODEL}" in capsys.readouterr().out + + assert run("model-binding", "edit", *root, *declaration, + "--model", "another-model", "--", "openprofiler-broker") == 0 + capsys.readouterr() + assert store.get("local-chat").model == "another-model" + + # `edit` replaces the whole binding, so an edit without `--model` declares + # none, and the list says what the request then names + assert run("model-binding", "edit", *root, *declaration, + "--", "openprofiler-broker") == 0 + capsys.readouterr() + assert store.get("local-chat").model is None + assert run("model-binding", "list", *root) == 0 + from opendox import cli_model_binding as cmb + assert cmb.NO_MODEL_DECLARED in capsys.readouterr().out + + # a blank model refuses THROUGH THE VERB, not only through the record + assert run("model-binding", "edit", *root, *declaration, + "--model", " ", "--", "openprofiler-broker") == 1 + capsys.readouterr() + assert store.get("local-chat").model is None + + +def test_a_stand_in_chat_server_receives_the_declared_model(tmp_path): + with _stand_in_provider(_ChatCompletionsHandler) as base: + binding = _broker_binding( + _write_broker(tmp_path), endpoint=f"{base}/v1/chat/completions", + dialect=OPENAI_CHAT, model=DECLARED_MODEL) + provider_mod.BrokeredProviderPort( + binding, install_mod.brokered_catalog(binding), + notice=lambda _text: None).dispatch(_Envelope()) + assert _ChatCompletionsHandler.seen["body"] == { + "model": DECLARED_MODEL, + "messages": [{"role": "user", "content": "assembled prompt"}]} From 6e1b8942913f650a182a3bd5999fcd04586f689f Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:10:09 +0000 Subject: [PATCH 3/5] T078: one throwing call per pytest.raises block in the new tests (SonarCloud) SonarCloud's analysis of openDox-code#61 flagged five of T078's new test lines: four `pytest.raises` blocks whose envelope was built inside the block (S5778), and one composite assertion (S9073). The envelope is now built before each block and the assertion is split in two. No assertion changes meaning, and the case count is unchanged. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_model_provider_broker.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_model_provider_broker.py b/tests/test_model_provider_broker.py index 9fe6099e..76cbd2ec 100644 --- a/tests/test_model_provider_broker.py +++ b/tests/test_model_provider_broker.py @@ -1429,16 +1429,18 @@ def test_the_prompt_dialect_is_unchanged_byte_for_byte(tmp_path): "content-not-text", "the-other-grammar"]) def test_a_chat_answer_off_the_declared_path_is_malformed(tmp_path, answer): port, _opener = _port(tmp_path, answer, dialect=OPENAI_CHAT) + envelope = _Envelope() with pytest.raises(provider_mod.BrokerRefused) as caught: - port.dispatch(_Envelope()) + port.dispatch(envelope) assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_MALFORMED def test_a_chat_shaped_answer_is_not_the_prompt_grammars_answer(tmp_path): """Each arm reads its own grammar and no other.""" port, _opener = _port(tmp_path, _chat_completion()) + envelope = _Envelope() with pytest.raises(provider_mod.BrokerRefused) as caught: - port.dispatch(_Envelope()) + port.dispatch(envelope) assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_MALFORMED @@ -1465,15 +1467,17 @@ def test_the_expiry_ruling_holds_for_the_chat_grammar(tmp_path): provider_mod.REASON_EXPIRY_REMINT, provider_mod.REASON_PAID_RETRY, ] - assert printed and "re-minted once and retried" in printed[0] + assert printed + assert "re-minted once and retried" in printed[0] def test_the_answer_bound_holds_for_the_chat_grammar(tmp_path): bound = provider_mod.MAX_PROVIDER_ANSWER_BYTES oversize = json.dumps(_chat_completion("x" * bound)).encode("utf-8") port, _opener = _port(tmp_path, oversize, dialect=OPENAI_CHAT) + envelope = _Envelope() with pytest.raises(provider_mod.BrokerRefused) as caught: - port.dispatch(_Envelope()) + port.dispatch(envelope) assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_MALFORMED @@ -1483,8 +1487,9 @@ def test_a_chat_provider_refusal_lands_on_the_fixed_sentence(tmp_path): urllib.error.HTTPError(ENDPOINT, 400, "Bad Request", {}, io.BytesIO(b'{"error":{"message":"leaky"}}')), dialect=OPENAI_CHAT) + envelope = _Envelope() with pytest.raises(provider_mod.BrokerRefused) as caught: - port.dispatch(_Envelope()) + port.dispatch(envelope) assert caught.value.diagnostic == provider_mod.DIAG_PROVIDER_REFUSED assert "leaky" not in str(caught.value) From 053e207a7b6b60c121e5803bead0c91599ff9733 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:15:17 +0000 Subject: [PATCH 4/5] T079: the intake module's docstring stops counting the binding's fields Copilot's overview of openDox-code#62 noted that doxbench_intake.py's module docstring still called the binding a "closed nine-field record" and the intake declaration "not a tenth field on it". T079 made the record ten fields, so both phrases were false. The paragraph now names each count by its tuple, as it already did for the catalog entry, and records that the binding grew once, by `model` (#1144 box 16.2). The declaration is "not a field on it". This is a docstring only: no code, no test and no behaviour changes. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) --- src/opendox/doxbench_intake.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/opendox/doxbench_intake.py b/src/opendox/doxbench_intake.py index caaeb4a6..a51d9cfa 100644 --- a/src/opendox/doxbench_intake.py +++ b/src/opendox/doxbench_intake.py @@ -20,21 +20,22 @@ never sees a credential — which is why it is safe for it to be the surface a browser talks to. -WHAT IT DOES NOT WIDEN, also deliberately: the BINDING's closed nine-field -record (`doxbench_binding.BINDING_FIELDS`) and the CATALOG entry's closed public -shape (`doxbench_model.DECLARABLE_ENTRY_FIELDS`). The count is named by that -tuple rather than restated here, because the shape has grown twice by governing -release since this module was written — the routing declaration at -contract-v1.38 and the input-modality declaration at contract-v2.2 — and this -paragraph's claim is that THIS module widens nothing, which is unchanged by -either. Proposed-versus- +WHAT IT DOES NOT WIDEN, also deliberately: the BINDING's closed record +(`doxbench_binding.BINDING_FIELDS`) and the CATALOG entry's closed public +shape (`doxbench_model.DECLARABLE_ENTRY_FIELDS`). Each count is named by its +tuple rather than restated here, because both shapes have grown since this +module was written. The catalog's grew twice by governing release (the routing +declaration at contract-v1.38 and the input-modality declaration at +contract-v2.2), and the binding's grew once, by `model` (#1144 box 16.2, plan +034 T079). This paragraph's claim is that THIS module widens nothing, which is +unchanged by any of them. Proposed-versus- approved is a SERVER-SIDE distinction and a pending declaration is simply not in the catalog, so NEITHER SHAPE GAINS A FIELD FROM THIS MODULE and this module needs no release act. (Both statements are scoped to this module deliberately. The catalog shape HAS gained fields — by the governing releases named above — and each of those was a release act; what has never happened, and is what this paragraph promises, is this module widening either shape.) The declaration is a -SECOND record beside the binding, not a tenth field on it. +SECOND record beside the binding, not a field on it. WHY PENDING-NESS IS A DECLARED FACT AND NOT A DEFAULT. A binding this document says nothing about is UNAFFECTED: it resolves exactly as it resolved before this From e7f3a7b3415a1aec1371d297c926258d7f10c936 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Sat, 3 Oct 2026 00:20:19 +0000 Subject: [PATCH 5/5] T079: doxbench_intake's docstring names each shape that grew (Copilot at cb059d5d) Copilot's review of openDox-code#62 at cb059d5d read "the catalog's grew" and "the binding's grew" as possessives used as verbs. Each was elliptical for the shape it named, and the review shows a reader can miss that, so the paragraph now names the noun: the catalog entry's shape, and the binding's record. A docstring only. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) --- src/opendox/doxbench_intake.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/opendox/doxbench_intake.py b/src/opendox/doxbench_intake.py index a51d9cfa..74fead6f 100644 --- a/src/opendox/doxbench_intake.py +++ b/src/opendox/doxbench_intake.py @@ -24,11 +24,11 @@ (`doxbench_binding.BINDING_FIELDS`) and the CATALOG entry's closed public shape (`doxbench_model.DECLARABLE_ENTRY_FIELDS`). Each count is named by its tuple rather than restated here, because both shapes have grown since this -module was written. The catalog's grew twice by governing release (the routing -declaration at contract-v1.38 and the input-modality declaration at -contract-v2.2), and the binding's grew once, by `model` (#1144 box 16.2, plan -034 T079). This paragraph's claim is that THIS module widens nothing, which is -unchanged by any of them. Proposed-versus- +module was written. The catalog entry's shape grew twice by governing release +(the routing declaration at contract-v1.38 and the input-modality declaration +at contract-v2.2), and the binding's record grew once, by `model` (#1144 box +16.2, plan 034 T079). This paragraph's claim is that THIS module widens +nothing, which is unchanged by any of them. Proposed-versus- approved is a SERVER-SIDE distinction and a pending declaration is simply not in the catalog, so NEITHER SHAPE GAINS A FIELD FROM THIS MODULE and this module needs no release act. (Both statements are scoped to this module deliberately.