Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4514a9e
T078: 16.1, the OpenAI-compatible dialect joins DIALECTS (plan 034)
brettheap Sep 28, 2026
7c83c2c
T079: 16.2, a `model` field the provider receives (plan 034)
brettheap Sep 28, 2026
e9ef951
T080: 16.3, the credential stays a reference; a raw key is refused (p…
brettheap Sep 28, 2026
6e1b894
T078: one throwing call per pytest.raises block in the new tests (Son…
brettheap Sep 28, 2026
a63dcb8
Merge T078's SonarCloud test fix into T079's branch
brettheap Sep 28, 2026
053e207
T079: the intake module's docstring stops counting the binding's fields
brettheap Sep 28, 2026
15adc2b
Merge T079's branch (T078's SonarCloud fix, the intake docstring) int…
brettheap Sep 28, 2026
f92fca4
T080: bound the endpoint before the detector; present only what a bea…
brettheap Sep 28, 2026
68b6e41
T080: the stand-in server's record is reset through monkeypatch (Sona…
brettheap Sep 28, 2026
146b5a2
T080: the console flow's kinds are the ones a broker enrols, said and…
brettheap Sep 28, 2026
4abc6d4
T080: a built-in credential travels only over https:// or to this host
brettheap Sep 28, 2026
d240fd5
T080: the endpoint's checks and the private-route rule move into two …
brettheap Sep 28, 2026
5167084
T080: a request carrying a built-in credential follows no redirect
brettheap Sep 28, 2026
1b0fb3f
T080: no frame a refusal keeps holds a raw credential
brettheap Sep 28, 2026
286655f
T080: a built-in credential over plain http:// uses no proxy
brettheap Sep 28, 2026
3f14bb9
T080: a broker's reference may not take a built-in form; keyring fail…
brettheap Sep 28, 2026
d07b937
Merge main 047bb4fa into T078 (phase 2 has landed)
brettheap Oct 2, 2026
b04a3a9
Merge T078's head d07b9371 into T079 (main 047bb4fa)
brettheap Oct 2, 2026
4948e6d
Merge T079's head b04a3a95 into T080 (main 047bb4fa)
brettheap Oct 2, 2026
cb059d5
Merge main 8a98e317 into T079 (T078 landed as #61)
brettheap Oct 3, 2026
645280a
T080: refuse a raw key's shape, fix the scheme refusal, map http.clie…
brettheap Oct 3, 2026
03fb779
Merge T079's head cb059d5d into T080 (main 8a98e317, T078 landed)
brettheap Oct 3, 2026
e7f3a7b
T079: doxbench_intake's docstring names each shape that grew (Copilot…
brettheap Oct 3, 2026
abbb05d
Merge T079's head e7f3a7b3 into T080 (Copilot's docstring fix on #62)
brettheap Oct 3, 2026
93660ec
Merge main 2fc714d2 into T080 (T079 landed as #62)
brettheap Oct 3, 2026
82ec9a2
T080: the scheme refusal says nothing of hosts (Copilot at abbb05d4)
brettheap Oct 3, 2026
67d0061
T080: SonarCloud at 82ec9a20 (S5713 in the transport, S5778 x4 in tests)
brettheap Oct 3, 2026
44582f8
T080: a keyring package that fails as it is imported refuses like a f…
brettheap Oct 3, 2026
47c9da9
T080: the L4 case runs a broker's turn too (Copilot at 44582f8f)
brettheap Oct 3, 2026
05cb1c7
Merge main 9a490405 into T080 (T081 landed as #74)
brettheap Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 59 additions & 16 deletions src/opendox/cli_model_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,27 @@ def _declared_binding(args: argparse.Namespace) -> "binding_mod.ModelProviderBin
credential_ref=args.credential_ref, auth_kind=args.auth_kind,
approved_by=args.approved_by, endpoint=args.endpoint,
dialect=args.dialect, model=args.model,
broker_argv=tuple(args.broker_argv))
broker_argv=tuple(args.broker_argv or ()))


#: 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)"

#: What `list` prints for a field the record's resolver forbids or does not
#: need (#1144 box 16.3): the reference under the auth kind `none`, and the
#: broker invocation of a record no broker answers. The custody line beside it
#: says which resolver answers instead.
NOT_DECLARED = "(none)"

#: What `set-credential` says of a binding no broker answers (#1144 box 16.3).
#: There is no broker to hand a credential to: the built-in resolver reads the
#: reference at call time, or the endpoint takes none.
NO_BROKER_TO_HAND_TO = (
"binding {binding_id!r} names no broker, so there is nothing to hand a "
"credential to: {custody}")


def cmd_model_binding_list(args: argparse.Namespace) -> int:
"""DISCLOSE every declared binding (task 1.2's read-back).
Expand All @@ -81,13 +94,16 @@ def cmd_model_binding_list(args: argparse.Namespace) -> int:
print(f" provider {record['provider']}")
print(f" auth kind {record['auth_kind']}")
print(f" approved by {record['approved_by']}")
print(f" credential ref {record['credential_ref']}")
reference = record["credential_ref"]
print(f" credential ref "
f"{reference if reference is not None else NOT_DECLARED}")
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']}")
argv = record["broker_argv"]
print(f" broker argv {argv if argv else NOT_DECLARED}")
print(f" custody {record['credential_custody']}")
return 0

Expand All @@ -100,7 +116,7 @@ def cmd_model_binding_add(args: argparse.Namespace) -> int:
print(str(exc), file=sys.stderr)
return 1
print(f" declared {binding.id} in {store.path}")
print(f" {binding_mod.CUSTODY_NOTICE}")
print(f" {binding.custody_notice()}")
return 0


Expand All @@ -123,7 +139,7 @@ def cmd_model_binding_remove(args: argparse.Namespace) -> int:
print(str(exc), file=sys.stderr)
return 1
print(f" retired {binding.id} from {store.path}")
print(f" {binding_mod.REMOVAL_NOTICE}")
print(f" {binding.removal_notice()}")
return 0


Expand All @@ -137,7 +153,12 @@ def cmd_model_binding_set_credential(args: argparse.Namespace, *,
standard input. No variable in this function ever holds the credential, so
none can outlive the call, be echoed in a message, or reach an exception.
It is deliberately NOT a command-line argument: an argv is visible in the
process table and lands in a shell history."""
process table and lands in a shell history.

A BINDING NO BROKER ANSWERS IS REFUSED, and its standard input is left
unread (#1144 box 16.3). Its credential is where its `env:` or `keyring:`
reference names, or it takes none, so there is no custodian to hand a
value to, and reading one here would be holding it for nothing."""
from opendox import doxbench_provider as provider_mod

store = _binding_store(args)
Expand All @@ -146,6 +167,10 @@ def cmd_model_binding_set_credential(args: argparse.Namespace, *,
if binding is None:
raise binding_mod.BindingRefused(
f"no binding with id {args.id!r} is declared")
if (binding.credential_source()
!= binding_mod.CREDENTIAL_FROM_BROKER):
raise binding_mod.BindingRefused(NO_BROKER_TO_HAND_TO.format(
binding_id=binding.id, custody=binding.custody_notice()))
reference = provider_mod.hand_off_credential(
binding, source if source is not None else sys.stdin)
store.edit(dataclasses.replace(binding, credential_ref=reference))
Expand All @@ -171,15 +196,25 @@ def _add_binding_declaration_args(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--label", required=True,
help="the label the model menu shows")
parser.add_argument("--provider", required=True,
help="the provider name the broker takes custody for "
"(the broker's declared `--provider`)")
parser.add_argument("--credential-ref", required=True,
help="the provider's name; where a broker holds the "
"credential, the one it takes custody for (the "
"broker's declared `--provider`)")
# A REFERENCE, NEVER THE CREDENTIAL (#1144 box 16.3). NOT REQUIRED by the
# parser, because the auth kind `none` forbids it. The binding itself
# refuses a missing reference for every kind that takes a credential, and
# says why.
parser.add_argument("--credential-ref", default=None,
dest="credential_ref",
help="the reference the broker resolves; NEVER the "
"credential itself")
help="the credential's REFERENCE, NEVER the credential "
"itself: env:NAME or keyring:SERVICE/USERNAME, "
"which the built-in resolver reads at call time, "
"or a reference the broker resolves; omitted "
f"for --auth-kind {binding_mod.AUTH_KIND_NONE}")
parser.add_argument("--auth-kind", required=True, dest="auth_kind",
choices=list(binding_mod.AUTH_KINDS),
help="the authentication kind the broker holds")
help="the authentication kind of the credential; "
f"{binding_mod.AUTH_KIND_NONE} for an endpoint "
"that takes none")
# REQUIRED because the broker requires it: `credential-contracts` holds
# that a grant without an approver is invalid, and the broker's `intake`
# refuses without an approver flag. A binding that could not name one could
Expand Down Expand Up @@ -208,8 +243,10 @@ def _add_binding_declaration_args(parser: argparse.ArgumentParser) -> None:
# neither an endpoint nor a dialect from a mint, deliberately, so both are
# declared here — see doxbench_binding's module docstring.
parser.add_argument("--endpoint", required=True,
help="the provider endpoint this binding's minted "
"token is presented at")
help="the provider endpoint this binding's requests "
"are sent to; a URL carrying a credential is "
"refused, so name the credential by its reference "
"instead")
parser.add_argument("--dialect", required=True,
choices=list(binding_mod.DIALECTS),
help="the request grammar that endpoint speaks")
Expand All @@ -230,11 +267,17 @@ def _add_binding_declaration_args(parser: argparse.ArgumentParser) -> None:
# rewriting the operator's store path and truncating their template. The
# subparsers below also set `allow_abbrev=False`, so the two defences are
# independent.
#
# ZERO OR MORE since #1144 box 16.3: a binding the built-in resolver or the
# auth kind `none` answers names no broker, and one given beside either is
# refused by the binding itself. A broker's reference still needs its
# invocation, and the binding still refuses one without it.
parser.add_argument(
"broker_argv", nargs="+", metavar="-- BROKER ARGV",
"broker_argv", nargs="*", metavar="-- BROKER ARGV",
help="the broker invocation, as argv members, after a bare `--`. "
f"Placeholders {binding_mod.ARGV_PLACEHOLDERS} are filled from "
"this binding's own fields")
"this binding's own fields. Omitted for an env: or keyring: "
f"reference and for --auth-kind {binding_mod.AUTH_KIND_NONE}")


def _add_model_binding_parser(sub) -> None:
Expand Down
Loading
Loading