1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! The ONE place the `${VAR}` / `env:VAR` reference grammar is defined for the
//! whole toolkit.
//!
//! Every path that reads an operator-supplied value which MAY be an environment
//! reference routes through [`parse_env_ref`]:
//!
//! - outgoing credentials (`[backend.auth]` api_key / bearer token / basic
//! password / oauth2 client_secret) — `crate::http::auth`;
//! - `[code_mode] token_secret` — `crate::code_mode`;
//! - `[backend] base_url` — [`crate::config::BackendSection::resolved_base_url`].
//!
//! # Why this module is NOT feature-gated
//!
//! The function previously lived as a private helper inside `crate::http::auth`,
//! and the whole `http` module is `#[cfg(feature = "http")]`. It compiled
//! (`BackendSection` is itself http-gated), but the accompanying claim — "the
//! single chokepoint every env-reference path shares" — was reachable only in
//! `http` builds, so it was architecturally false. Packaging tooling is about to
//! build a cross-crate grammar-parity claim on top of that statement, so the
//! statement has to be structurally true rather than aspirational: this module
//! carries NO `#[cfg(feature = ...)]` gate and compiles in every feature
//! configuration.
//!
//! # Grammar
//!
//! | Input | Result | Meaning |
//! |---|---|---|
//! | `env:VAR` | `Some("VAR")` | reference |
//! | `${VAR}` | `Some("VAR")` | reference (`VAR` must match `[A-Za-z0-9_]+`) |
//! | `${}` | `Some("")` | MALFORMED reference — a reference to an empty name |
//! | `${A}://${B}` | `Some("")` | MALFORMED reference — a multi-placeholder composition |
//! | `${VAR` | `None` | unterminated → a plain literal |
//! | `plain` | `None` | plain literal |
//!
//! The malformed-means-empty rule is deliberate: the empty name is the signal
//! that a value is reference-SHAPED but names nothing, so no caller ships the
//! literal `${...}` text to the wire. A `${...}` value is a reference to exactly
//! ONE variable — the grammar does not interpolate inside a larger string, so a
//! composition like `${SCHEME}://${HOST}` names nothing any environment could
//! set; compose the full value in one variable instead.
//!
//! # Callers diverge on UNSET, agree on MALFORMED, never diverge on PARSING
//!
//! Two different questions, two different answers:
//!
//! - **Unset variable** (`Some("VAR")`, `VAR` not exported) — caller policy, and
//! it legitimately differs. A credential resolves it to the empty string so an
//! OPTIONAL credential is omitted; an endpoint errors, because an empty
//! endpoint is not a degraded request but a broken one.
//! - **Malformed reference** (`Some("")`) — NOT caller policy. Every caller
//! refuses it, because no environment could ever satisfy it, so "omit" would
//! mean silently proceeding without a value the operator believed they had
//! supplied. The credential path once applied the unset rule here, which made
//! `token = "${GITHUB-PAT}"` send every backend request unauthenticated with
//! no error and no log line; it is now refused at load time
//! ([`crate::error::ConfigValidationError::MalformedBackendAuthRef`]) and at
//! provider-build time, matching `base_url`'s
//! [`crate::error::ConfigValidationError::MalformedBackendBaseUrlRef`].
//!
//! What must NOT differ is the PARSE: a second `${}` parser with slightly
//! different edge cases is a latent security bug (one parser treating `${VAR` as
//! a reference and another as a literal is exactly how a placeholder reaches the
//! wire).
//!
//! Note that `pmcp-package` deliberately DUPLICATES this grammar rather than
//! depending on the toolkit — that crate is the workspace-excluded leaf and a
//! dependency in either direction inverts the layering. The duplication is kept
//! honest by a package-side grammar-parity table, not by a shared type.
/// The single brace/env-ref parse core shared by EVERY credential-resolution
/// path (api_key, bearer token, basic password, oauth2 client_secret).
///
/// Returns `Some(var_name)` when `raw` is a secret REFERENCE — either the
/// `"env:VAR"` or the `"${VAR}"` form — and `None` for a plain literal (which the
/// caller uses verbatim). A malformed brace reference (e.g. `"${}"`) is treated
/// as a reference to an empty name, i.e. `Some("")`, so the caller resolves it to
/// the empty string (omission) rather than shipping the literal `${}`.
///
/// This consolidates the two brace parsers that previously existed (the inline
/// `${`-strip in the old api_key resolver in `crate::http::auth` and
/// `expand_braced_var` in `crate::code_mode`): all env-reference resolution now
/// flows through this one chokepoint so the discipline cannot drift per-variant.
///
/// # Why this is `pub`
///
/// The grammar is duplicated by necessity in `pmcp-package`'s
/// `is_env_reference` — that crate is the workspace-excluded leaf and neither
/// crate may depend on the other. The two implementations are held to a shared
/// accept/reject table asserted from an INTEGRATION test in each crate
/// (`tests/env_ref_grammar_parity.rs` here). An integration test is an external
/// consumer, so the reference implementation has to be reachable from outside
/// the crate for that parity claim to be checkable at all.
///
/// (It was previously `pub(crate)` with an `#[allow(dead_code)]`, because in a
/// `--no-default-features` build neither `http` nor `code-mode` is compiled and
/// no in-crate caller exists. Being `pub` removes that need: the module is
/// still deliberately UNGATED, so the grammar is defined in one place that
/// compiles in every feature configuration.)
/// Whether `name` is a variable name a target environment can actually be
/// told to set: non-empty, ASCII alphanumerics and `_` only — the portable
/// intersection of POSIX shells, env files, and container manifests.
///
/// The `${NAME}` form requires this (it is the form config authors compose
/// by accident — `${SCHEME}://${HOST}` must not silently become one garbage
/// reference). The explicit `env:NAME` form deliberately does NOT: its prefix
/// is unambiguous, so it stays the runtime escape hatch for exotic names.