@yanlinglabs/winter-provider-catalog 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +59 -0
- package/PROVENANCE.md +598 -0
- package/README.md +46 -0
- package/UPSTREAM.json +10 -0
- package/dist/extract/fetch.d.ts +62 -0
- package/dist/extract/ledgers.d.ts +113 -0
- package/dist/extract/literal-extractor.d.ts +94 -0
- package/dist/extract/merge.d.ts +213 -0
- package/dist/families.d.ts +55 -0
- package/dist/families.js +30 -0
- package/dist/index-t40pzh81.js +94 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +40027 -0
- package/dist/types.d.ts +385 -0
- package/dist/validate.d.ts +98 -0
- package/generated/catalog.json +39282 -0
- package/generated/denominator.json +65 -0
- package/generated/rejections.json +5780 -0
- package/generated/upstream-layer.json +37125 -0
- package/overlay/families.json +600 -0
- package/overlay/models.json +4691 -0
- package/overlay/providers.json +2383 -0
- package/package.json +50 -0
- package/schema/catalog.schema.json +238 -0
package/PROVENANCE.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# Provenance — how `generated/catalog.json` came to exist
|
|
2
|
+
|
|
3
|
+
## Layers
|
|
4
|
+
|
|
5
|
+
The committed catalog is the merge of two layers, performed by `scripts/provider-catalog.ts`
|
|
6
|
+
(`bun run provider:catalog`):
|
|
7
|
+
|
|
8
|
+
| Layer | Source | Owner | Present |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| upstream | `generated/upstream-layer.json`, extracted from the pinned OmniRoute tree by `scripts/provider-source-sync.ts` | the extractor | **yes** — 106 providers, 540 models |
|
|
11
|
+
| overlay | `overlay/providers.json` + `overlay/models.json`, hand-authored and reviewed | Winter | yes — 64 providers, 65 models |
|
|
12
|
+
|
|
13
|
+
**The overlay always wins.** WS-13 §7: live discovery and upstream extraction never silently
|
|
14
|
+
overwrite `official-doc`/`live-probe` overlay entries, so a conflicting upstream row is dropped in
|
|
15
|
+
the overlay's favour rather than merged field-by-field. Rows are then sorted by `id`/`key` so a
|
|
16
|
+
regeneration is byte-identical.
|
|
17
|
+
|
|
18
|
+
A re-sync **never writes the overlay**. `scripts/provider-source-sync.ts` has no code path that
|
|
19
|
+
opens `overlay/*.json` for writing, it re-reads both files after every run and fails loudly if their
|
|
20
|
+
bytes moved, and `src/extract/pipeline.test.ts` proves an edited overlay survives a re-sync
|
|
21
|
+
byte-for-byte.
|
|
22
|
+
|
|
23
|
+
## The pin
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{ "tag": "v3.8.50",
|
|
27
|
+
"tagObject": "6f5d4e00e817bc01b2ac16fdd66db3840c296416",
|
|
28
|
+
"commit": "5458026c216f77a3da68ea49152dc33470cfe2cb" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`6f5d4e00…` is the **annotated tag's own object**, not a commit. The OmniRoute report records it as
|
|
32
|
+
"resolving to" v3.8.50, and taking it as the commit would have pinned nothing that a re-tag could
|
|
33
|
+
not move: `git clone --depth 1 --branch v3.8.50` prints *"refs/tags/v3.8.50 6f5d4e00… is not a
|
|
34
|
+
commit!"* and checks out `5458026c…`. Winter records **both** and refuses to run when either fails
|
|
35
|
+
to match (`src/extract/fetch.ts`).
|
|
36
|
+
|
|
37
|
+
`catalogVersion` is therefore `v3.8.50+winter.1` — the upstream release plus the extractor revision,
|
|
38
|
+
the recoverability WS-13 §2 requires. (The spec's illustrative spelling is `3.8.50-winter.1`; the
|
|
39
|
+
composed string is the frozen builder's, `${tag}+${extractorVersion}`, and carries the same two
|
|
40
|
+
facts.)
|
|
41
|
+
|
|
42
|
+
## What upstream contributed, and what it did not
|
|
43
|
+
|
|
44
|
+
Extraction is a **literal walk**, never an evaluation: `src/extract/literal-extractor.ts` hands file
|
|
45
|
+
text to the TypeScript parser and reads object/array literals. No upstream module is imported,
|
|
46
|
+
loaded, or executed anywhere in this repository, and git itself runs with `core.hooksPath` pointed
|
|
47
|
+
at an empty directory so a hostile repository cannot execute either.
|
|
48
|
+
|
|
49
|
+
309 upstream files were materialized into a scratch checkout that was deleted before the run ended.
|
|
50
|
+
**Two** of them exist in this repository: `third_party/omniroute-provider-source/LICENSE` and
|
|
51
|
+
`.../NOTICE`, copied verbatim with their blob ids and sha256 recorded in `extraction-manifest.json`.
|
|
52
|
+
No executor, translator, or helper source was copied — the NOTICE register says so, and it is empty
|
|
53
|
+
of code entries on purpose.
|
|
54
|
+
|
|
55
|
+
### Field-by-field classification
|
|
56
|
+
|
|
57
|
+
Report §6 asks for this table; `src/extract/ledgers.ts`'s `FIELD_PROVENANCE` is its machine-readable
|
|
58
|
+
twin and a test asserts the two agree, so this document cannot drift away from the mapper.
|
|
59
|
+
|
|
60
|
+
| Field | Provenance | Note |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `provider.id` | mechanically normalized | upstream id through the allowlist's reviewed `winterId` map — upstream's `gemini` is Winter's `google`; every other id is verbatim |
|
|
63
|
+
| `provider.displayName` | copied verbatim | the product-catalog row's `name` |
|
|
64
|
+
| `provider.protocols` | mechanically normalized | upstream `format` through a closed map; an unknown format **fails the run** |
|
|
65
|
+
| `provider.authKinds` | mechanically normalized | upstream `authType` through a closed map; an unknown auth value **fails the run** |
|
|
66
|
+
| `provider.defaultEndpoints` | mechanically normalized | `api` is upstream's `baseUrl` with exactly the path its own `format` names removed (`/chat/completions`, `/responses`, `/v1/messages`, `/v1beta/models`) — the API **root**, and the exact inverse of what every adapter in that family appends to `connection.baseUrl`. Never a trim to an origin. A `default`-executor row whose URL does not end in its format's suffix, or states none, **fails the run**. `bedrock`/`vertex` are exempt and stay verbatim. `responsesBaseUrl`/`modelsUrl` are copied verbatim. A URL carrying userinfo or a query string is dropped and recorded (R6-11) |
|
|
67
|
+
| `provider.modelDiscovery` | mechanically normalized | derived from `modelsUrl`/`passthroughModels`; upstream has no such field |
|
|
68
|
+
| `provider.liveCatalogAuthority` | mechanically normalized | upstream `liveCatalogAuthoritative` when **stated**; unstated becomes `unknown`, never upstream's `true` default |
|
|
69
|
+
| `provider.adapterId` / `provider.family` | local override | the Winter adapter family the protocol routes to; upstream's `executor` never crosses |
|
|
70
|
+
| `provider.risk` | local override | the reviewed allowlist row |
|
|
71
|
+
| `provider.upstream.{commit,sourcePaths}` | mechanically normalized | the pinned peeled commit and the paths the row was read from |
|
|
72
|
+
| `model.key` / `model.providerId` | mechanically normalized | `<winterId>/<upstream model id>` (WS-13 §8.3) |
|
|
73
|
+
| `model.upstreamId` | copied verbatim, **except** a reviewed correction | verbatim unless `allowlist.json`'s `modelOverrides` names it (today: OpenRouter's `auto` → `openrouter/auto`). Every correction is a `reviewed-normalization` ledger row, and the upstream spelling survives as an alias |
|
|
74
|
+
| `model.displayName` / `model.aliases` | copied verbatim | a duplicate id is dropped **and recorded**, never silently de-duplicated |
|
|
75
|
+
| `model.endpoints` | mechanically normalized | the provider's protocol, or the model's own `targetFormat` when it selects Responses within the same family |
|
|
76
|
+
| `model.contextWindow` / `model.maxInputTokens` / `model.maxOutputTokens` | copied verbatim | `contextLength` (falling back to the provider's `defaultContextLength`), `maxInputTokens`, `maxOutputTokens`; non-positive or non-integer values dropped |
|
|
77
|
+
| `model.inputModalities` | mechanically normalized | `text` plus `image`/`audio`/`video` from `supportsVision`/`supportsAudio`/`supportsVideo` |
|
|
78
|
+
| `model.outputModalities` | local override (a **Winter default**) | upstream declares NO output modality for any model, so `["text"]` is Winter's inference — see "the output-modality stamp" below |
|
|
79
|
+
| `model.toolCalling` / `model.nativeTools` | mechanically normalized | upstream `toolCalling` → `native`/`none`; **absent → `none` at `confidence: "unknown"`** |
|
|
80
|
+
| `model.reasoning.{supported,efforts,continuation}` | mechanically normalized | `supportsReasoning` / `supportedThinkingEfforts` / the provider's `reasoningTransport`; an unstated transport becomes `none` |
|
|
81
|
+
| `model.unsupportedParameters` | copied verbatim | when it is an accepted literal; `Object.freeze([...])` is a call expression and is rejected |
|
|
82
|
+
| `model.status` | local override | `candidate` by default; `experimental` where the allowlist's reviewed `initialModelStatus` says so (R6-16's native cloud, 4 of 53 extracted rows), overridable per row. Never `supported` — upstream presence promotes nothing |
|
|
83
|
+
| `*.pricing` | official-doc derived | **overlay only**, from the vendors' pricing pages with the URL and observation instant |
|
|
84
|
+
| `*.classifierEligible` | live-probe proven | **never set** by extraction or overlay (R6-14) |
|
|
85
|
+
| `reasoning.continuationDomain` / `summaryRequest` / `readableState` / `completionEvent` / `toolLoopRequirement` | official-doc derived | **overlay only**; continuation domain is never inferred from a shared HTTP shape |
|
|
86
|
+
|
|
87
|
+
### Two tiers of admission citation
|
|
88
|
+
|
|
89
|
+
Every provider row carries `admission.citation` — the document that admits it (WS-13b §1, D21;
|
|
90
|
+
R6b-3 makes a row without one a validation failure, and one citing the audit's `unknown` class a
|
|
91
|
+
refusal). The rows do **not** all rest on the same strength of evidence, and conflating the two
|
|
92
|
+
tiers would be the quiet failure this field exists to prevent, so they are labelled:
|
|
93
|
+
|
|
94
|
+
| Tier | What it is | Which rows |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| **fetched-document** | a page `docs/research/Provider-third-party-access-audit.md` retrieved and read on 2026-09-06, or a vendor pricing page already reviewed in-repo | the 3 frontier pricing pages, and every P6.5 **overlay** row (the audit's 51 citations) |
|
|
97
|
+
| **pinned-upstream** | the vendor's own site as OmniRoute's product catalog records it at commit `5458026`, blob-pinned in `extraction-manifest.json`, plus the api-key path attested by that id's own pinned `RegistryEntry` (`authType: "apikey"`, its dialect, its base URL) | the 107 P6.5 allowlist admissions, and the 5 P6 rows T1 had left citing `spec:WS-13 §1` |
|
|
98
|
+
|
|
99
|
+
The two tiers above are the two that carry the argument. The full vocabulary has five, and the
|
|
100
|
+
CENSUS below is **generated from the shipped catalog** rather than counted by hand — a hand-counted
|
|
101
|
+
total in a document that describes a data field is a second, unpinned copy of that field, and it is
|
|
102
|
+
the copy a reader trusts.
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
bun run provenance:tiers # rewrite the census block below from the shipped catalog
|
|
106
|
+
bun run provenance:tiers -- --check # exit 1 if the committed block has drifted from the catalog
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The `--check` form is already gated: `scripts/provenance-tiers.test.ts` re-renders the census and
|
|
110
|
+
asserts the committed block is byte-identical, and that test runs under the repository's own
|
|
111
|
+
`bun test` in CI. The named script above is for a contributor who has just regenerated the catalog
|
|
112
|
+
and wants to refresh this document by name — not a second gate.
|
|
113
|
+
|
|
114
|
+
<!-- BEGIN GENERATED: admission-tier census (bun run scripts/provenance-tiers.ts) -->
|
|
115
|
+
|
|
116
|
+
Generated from `generated/catalog.json` (`v3.8.50+winter.1`, 165 provider rows). Do not edit by hand.
|
|
117
|
+
|
|
118
|
+
| Tier | Rows | What it means |
|
|
119
|
+
| --- | ---: | --- |
|
|
120
|
+
| **fetched-document** | 39 | a vendor page this repository retrieved and read, on a recorded date |
|
|
121
|
+
| **pinned-upstream** | 101 | the vendor's own site as the pinned upstream product catalog records it, plus that id's own pinned entry — a real, dated reference, but NOT a page read here |
|
|
122
|
+
| **spec-ruling** | 3 | a ruling in an approved spec (or a user ruling recorded in one) admits the PATH; the row's own details are carried from a reviewed ledger entry — `anthropic`, `azure-ai`, `oci` |
|
|
123
|
+
| **local** | 12 | a local installation on the operator's own machine — there is no third party to be admitted by — `docker-model-runner`, `lemonade`, `llama-cpp`, `llamafile`, `lm-studio`, `mlx-gemma`, `mlx-qwen`, `ollama-local`, `oobabooga`, `triton`, `vllm`, `xinference` |
|
|
124
|
+
| **audit** | 10 | the in-repo third-party-access audit's own findings, which cite the documents it read — `aihorde`, `cline`, `clinepass`, `codex-oauth`, `kilocode`, `moonshot`, `opencode`, `openference`, `uncloseai`, `xai-oauth` |
|
|
125
|
+
|
|
126
|
+
**Promotion is two-key** (WS-13b §1, fix-wave R-FW-3): a row leaves `pinned-upstream` only when a fetched vendor document AND a live-gate pass both exist, and no `approved` row or `supported` model may sit on that tier while it does not.
|
|
127
|
+
|
|
128
|
+
<!-- END GENERATED: admission-tier census -->
|
|
129
|
+
|
|
130
|
+
A pinned-upstream citation is a real, dated, verifiable reference — it names a specific blob at a
|
|
131
|
+
verified commit — but it is **not** a page this repository fetched and read, and it is not the
|
|
132
|
+
vendor's terms of service. Its own text says so, in every row. Two consequences are deliberate:
|
|
133
|
+
|
|
134
|
+
* every pinned-upstream row carries `risk.class: "review-required"` with that reason spelled out,
|
|
135
|
+
rather than `approved`. Only `blocked` refuses at resolution (`provider-runtime/src/registry.ts`),
|
|
136
|
+
so the row is usable — but nothing in the catalogue claims a review that did not happen;
|
|
137
|
+
* the audit's own `unknown` disposition is a **different and stronger** statement, and it excludes:
|
|
138
|
+
an id whose decisive document was looked for and not found is in `blocked`, not admitted at this
|
|
139
|
+
tier. `codebuddy-cn` is the worked example.
|
|
140
|
+
|
|
141
|
+
**A citation is checked for LIVENESS, not just for shape (round-1 finding I-3).** Every
|
|
142
|
+
`pinned-upstream` citation host was swept with a HEAD, a GET where the host refuses HEAD, and one
|
|
143
|
+
retry on 5xx. Eight rows cited something that is not a document and were moved to `blocked` with the
|
|
144
|
+
sweep result on the row: three NXDOMAIN (`llamagate`, `monsterapi`, `tokenrouter`), two HTTP 404
|
|
145
|
+
(`sumopod`, `token-kiosk`), one persistent 530 (`x5lab`), one HTTP 200 whose entire body is the
|
|
146
|
+
string "New API" (`chenzk` — a bare gateway shell), and one permanent redirect to a *different
|
|
147
|
+
company's* product page after an acquisition (`predibase` → `rubrik.com`). Two more were repaired
|
|
148
|
+
rather than dropped: `cerebras` cited a page that 301s to a chat product and now cites its Inference
|
|
149
|
+
API docs, and `zai-anthropic` cited the mainland product site although the lane had fetched z.ai's
|
|
150
|
+
own Claude-client doc. A citation is a row's entire evidence, so a citation that resolves to nothing
|
|
151
|
+
is a row with no evidence.
|
|
152
|
+
|
|
153
|
+
**What decision (a) asked for and why it could not be met as written.** The P6.5 plan asked lane X2
|
|
154
|
+
to upgrade five `spec:WS-13 §1` citations (`deepseek`, `openrouter`, `azure-openai`, `bedrock`,
|
|
155
|
+
`vertex`) "to vendor URLs from the audit's citations". The audit's 51 citations cover the OAuth,
|
|
156
|
+
keyless and agent-transport ids it audited; **none of the five appears in it**. They were upgraded to
|
|
157
|
+
the pinned-upstream tier instead, which is a real vendor URL and a strict improvement on a `spec:`
|
|
158
|
+
self-reference, and this paragraph is the record that the stronger upgrade was unavailable rather
|
|
159
|
+
than skipped.
|
|
160
|
+
|
|
161
|
+
### The two judgement calls worth arguing with
|
|
162
|
+
|
|
163
|
+
**Absent `toolCalling` becomes `none`, not `native`.** Upstream states tool calling on some rows and
|
|
164
|
+
not others, and an unstated capability is unknown. `native` is what makes a model agent-eligible, so
|
|
165
|
+
inferring it from silence would admit every unproven row to Code/Dispatch/Cowork/Build on a guess —
|
|
166
|
+
precisely the silent degradation WS-13 §8.1 prohibits. The cost is that upstream-derived Claude and
|
|
167
|
+
GPT rows report `toolCalling: none` until an overlay row or a live probe corrects them; the
|
|
168
|
+
confidence marker on each says `unknown` so nobody reads it as a denial.
|
|
169
|
+
|
|
170
|
+
**The output-modality stamp says `unknown`, and here is why it cannot say more.** Upstream's
|
|
171
|
+
`RegistryModel` has no output-modality field at all, so `["text"]` on every row is Winter's own
|
|
172
|
+
inference for a chat registry — not something upstream stated. It was shipped as
|
|
173
|
+
`source: "upstream-static", confidence: "inferred"`, which reads as *"upstream said text"*: a false
|
|
174
|
+
claim wearing an upstream label, and precisely the thing that let a text-to-speech model into the
|
|
175
|
+
catalog looking like a text model. Every row carries `confidence: "unknown"` and a `sourceRef`
|
|
176
|
+
that says WINTER DEFAULT in words, and the mapper now also stamps `source: "winter-default"` — the
|
|
177
|
+
member added for exactly this (`src/types.ts`), so a reader filtering evidence BY SOURCE no longer
|
|
178
|
+
gets a Winter guess wearing an upstream label. The prose stays alongside it, because *why* is not
|
|
179
|
+
something an enum can carry.
|
|
180
|
+
|
|
181
|
+
**That wrinkle is now closed, and how it stayed open is worth recording.** The committed
|
|
182
|
+
`generated/upstream-layer.json` is rewritten only by a NETWORK `provider:sync`, which is deliberately
|
|
183
|
+
absent from CI (a maintainer action, not a per-push gate). `--offline` re-merges what is on disk and
|
|
184
|
+
`provider:catalog` never re-extracts, so when the mapper changed to stamp `winter-default` the
|
|
185
|
+
committed layer kept saying `upstream-static`, `catalog.json` inherited it through the merge, and
|
|
186
|
+
**neither CI gate could see the gap** — the transition was pinned by a comment rather than by a
|
|
187
|
+
regeneration. P6.5 lane X2's first network run performed the sync: 99 evidence rows across the two
|
|
188
|
+
generated files moved in one commit, and `provider-source-sync --check` reports byte-identical
|
|
189
|
+
regeneration again. The general lesson is the one this document already makes about counts: a
|
|
190
|
+
generated file that only one un-gated command can write will drift, and the drift will look exactly
|
|
191
|
+
like a comment that is still true.
|
|
192
|
+
|
|
193
|
+
**It came back once, from another lane, and that is the more useful fact.** Lane O branched before
|
|
194
|
+
the fix and authored its two `xai-oauth` model rows on the pre-fix pattern — `upstream-static`, with
|
|
195
|
+
the same now-false justification that `EvidenceSource` has no member for a Winter default. Merging it
|
|
196
|
+
is what surfaced them, because `catalog-integrity.test.ts`'s I3 case asserts the property over the
|
|
197
|
+
MERGED document rather than over one lane's rows. Both were corrected in the merge. The lesson is
|
|
198
|
+
not about `xai-oauth`: a convention repaired in one branch is re-introduced by every branch that
|
|
199
|
+
forked before the repair, so the guard has to live on the merged artifact, and it did.
|
|
200
|
+
|
|
201
|
+
**The overlay's fifteen model rows were carrying the same false label, and a stale reason for it.**
|
|
202
|
+
Each stamped `outputModalities` as `upstream-static` with a `sourceRef` explaining that
|
|
203
|
+
*"`EvidenceSource` is frozen (src/types.ts) with no `winter-derived` member, so the caveat rides the
|
|
204
|
+
confidence marker and this ref"*. That was true when those rows were written and false by the time
|
|
205
|
+
the member was added — the mapper was updated, the fifteen hand-authored rows were not, and their
|
|
206
|
+
own justification went on citing a constraint that no longer existed. All fifteen now stamp
|
|
207
|
+
`winter-default` with a ref that says what is actually true. Nothing in the merged catalog claims
|
|
208
|
+
upstream stated an output modality any more, from either layer.
|
|
209
|
+
|
|
210
|
+
**`unsupportedParameters` fails OPEN, and that direction is deliberate.** `toolCalling` fails CLOSED
|
|
211
|
+
because a wrong `native` admits an unproven model to the agent modes; an empty
|
|
212
|
+
`unsupportedParameters` only means Winter will not *pre-reject* a parameter, and the provider's own
|
|
213
|
+
400 is the backstop. But an empty list can mean "upstream states none" OR "upstream states some as an
|
|
214
|
+
`Object.freeze([...])` we refuse to evaluate", and the two must not look alike — so every model in
|
|
215
|
+
the second case gets its own `unresolved-reference` ledger row naming the model and the consequence.
|
|
216
|
+
`openai/o3`, `o3-mini` and `o4-mini` are the ones at this pin — **three** rows, matched by the model's
|
|
217
|
+
ARRAY INDEX. The first version of that correlation matched by file, so a single refused field in
|
|
218
|
+
`openai/index.ts` produced a ledger row for all nineteen of its models: sixteen false claims, in a
|
|
219
|
+
ledger that is read as evidence.
|
|
220
|
+
|
|
221
|
+
**`Object.freeze([...])` is rejected like any other call.** "Accept a call when its callee looks
|
|
222
|
+
inert" is a rule that decays the first time upstream renames a helper, and the extractor's one
|
|
223
|
+
guarantee is that it never evaluates anything. The cost is visible and bounded: `o3`, `o3-mini` and
|
|
224
|
+
`o4-mini` lose their upstream `unsupportedParams`, which appear in `generated/rejections.json` under
|
|
225
|
+
`unresolved-reference` for a reviewer to see and the overlay to carry with real evidence.
|
|
226
|
+
|
|
227
|
+
## Endpoints diverge from upstream on purpose — and the divergence is now the MAPPER's, not the overlay's
|
|
228
|
+
|
|
229
|
+
Upstream's `baseUrl` is the full chat path (`https://api.openai.com/v1/chat/completions`). Winter's
|
|
230
|
+
adapters compose paths themselves, so what a descriptor must carry is the API **root**
|
|
231
|
+
(`https://api.openai.com/v1`). Until P6.5 only the OVERLAY said so: the extractor recorded upstream's
|
|
232
|
+
path verbatim and every upstream row was shadowed by a hand-authored overlay row that quietly
|
|
233
|
+
corrected it. The two layers therefore disagreed about the shape of this one field for the whole of
|
|
234
|
+
P6, and nothing failed — because no unshadowed upstream row had ever reached an adapter.
|
|
235
|
+
|
|
236
|
+
Widening the catalog is precisely what removes those shadows, so the disagreement was about to
|
|
237
|
+
become a hundred-odd rows that 404 at runtime. The mechanism has two links in different packages,
|
|
238
|
+
each reasonable on its own:
|
|
239
|
+
|
|
240
|
+
1. `runtime/src/provider/session-provider.ts`'s `connectionFrom` copies `defaultEndpoints.api` into
|
|
241
|
+
`connection.baseUrl` **as soon as more than one provider shares an adapter id**;
|
|
242
|
+
2. the adapter then appends its own protocol path —
|
|
243
|
+
`adapters/openai/chat-completions.ts`: `` `${endpoint.baseUrl}/chat/completions` ``.
|
|
244
|
+
|
|
245
|
+
Measured against a loopback fake: a row carrying the full path reaches
|
|
246
|
+
`/v1/chat/completions/chat/completions`.
|
|
247
|
+
|
|
248
|
+
So the mapper now records the root, by removing **exactly** the path the row's own upstream `format`
|
|
249
|
+
names (`FORMAT_ENDPOINT_SUFFIX` in `src/extract/merge.ts`) — the exact inverse of what the adapter
|
|
250
|
+
appends, and nothing more. A URL is still never trimmed to an ORIGIN; a `default`-executor row whose
|
|
251
|
+
URL does not end in its format's suffix, or states none at all, **fails the run** rather than
|
|
252
|
+
shipping with an absent `api` (an absent one is not inert — `resolveEndpoint` falls back to the
|
|
253
|
+
adapter's own vendor default, so the row would send that provider's credential to another vendor).
|
|
254
|
+
`bedrock`/`vertex` are exempt: single-provider adapters whose `api` is never copied into a
|
|
255
|
+
connection, and whose URLs are region/deployment templates rather than protocol paths.
|
|
256
|
+
|
|
257
|
+
**The evidence that this is the right transformation, rather than a convenient one:** five
|
|
258
|
+
independent human reviews had already performed this exact strip by hand, in `overlay/providers.json`,
|
|
259
|
+
before any of this code existed — and the rule reproduces all five byte-for-byte. `pipeline.test.ts`
|
|
260
|
+
→ *"THE PROOF THIS IS THE RIGHT TRANSFORM: it reproduces all five hand-authored overlay endpoints"*
|
|
261
|
+
pins that, and `runtime/src/provider/catalog-endpoint-shape.test.ts` measures the adapter's half
|
|
262
|
+
against a fake rather than describing it.
|
|
263
|
+
|
|
264
|
+
Every strip is a `reviewed-normalization` ledger row naming both strings, so the divergence stays
|
|
265
|
+
visible rather than laundered.
|
|
266
|
+
|
|
267
|
+
## The denominator (WS-13 §3 step 5)
|
|
268
|
+
|
|
269
|
+
Recomputed at every run into `generated/denominator.json`. At this pin:
|
|
270
|
+
|
|
271
|
+
> Enumerated product-catalog union at the pin: 352 distinct provider ids (per-category sum 352, no
|
|
272
|
+
> id appears in two categories). Backend REGISTRY entries: 270 (222 resolvable by the literal
|
|
273
|
+
> extractor; the remainder are built by a helper CALL, which is never evaluated). 82 catalogued
|
|
274
|
+
> id(s) have no backend registry entry; 0 registry id(s) have no product-catalog row. Upstream's own
|
|
275
|
+
> claims: README.md=352, docs/reference/PROVIDER_REFERENCE.md=352.
|
|
276
|
+
|
|
277
|
+
| Category | Ids | Winter disposition |
|
|
278
|
+
| --- | ---: | --- |
|
|
279
|
+
| noauth | 13 | blocked |
|
|
280
|
+
| oauth | 25 | blocked |
|
|
281
|
+
| web-cookie | 35 | blocked |
|
|
282
|
+
| apikey | 233 | candidate pool — 107 allowlisted, 126 rejected `not-allowlisted`, 0 named individually in `blocked` |
|
|
283
|
+
| local | 14 | Winter-owned (12 chat backends; `comfyui`/`sdwebui` excluded as image systems) |
|
|
284
|
+
| search | 14 | blocked |
|
|
285
|
+
| audio | 12 | blocked |
|
|
286
|
+
| upstream-proxy | 2 | blocked |
|
|
287
|
+
| cloud-agent | 3 | blocked |
|
|
288
|
+
| system | 1 | blocked |
|
|
289
|
+
|
|
290
|
+
**The 351/352 discrepancy does not reproduce at v3.8.50.** The research report recorded it at the
|
|
291
|
+
audited `b7a0c541…` head (declaring 3.8.51); at the pinned tag the enumerated union is 352 and both
|
|
292
|
+
of upstream's own claim sources also say 352. What the pin *does* show is a different and larger
|
|
293
|
+
gap the report did not name: **270 backend registry entries against 352 catalogued providers**, so
|
|
294
|
+
82 catalogued ids — `azure-openai` among them — have no executable entry at all. A catalog count is
|
|
295
|
+
not a support count, and at this pin it is not even a routable count.
|
|
296
|
+
|
|
297
|
+
The obligation is discharged as a standing computation rather than a restated pair of numbers: if a
|
|
298
|
+
future bump reintroduces a mismatch, `denominator.json` reports it without anyone editing prose.
|
|
299
|
+
|
|
300
|
+
## Two findings from the pin that a reviewer should see
|
|
301
|
+
|
|
302
|
+
**The retirement migrations are ABSENT here.** The report cites migrations `165`–`168` retiring
|
|
303
|
+
GPL-derived and provenance-hold integrations (Raycast Relay, Hailuo Web, Felo Web, Qwen Web, ChatGPT
|
|
304
|
+
Web). At v3.8.50 the migration series stops at `162`: those retirements landed **after** the pinned
|
|
305
|
+
tag. So the corresponding upstream rows are still live in this snapshot, and the only thing keeping
|
|
306
|
+
them out of Winter is Winter's own allowlist and category dispositions — not upstream's retirement.
|
|
307
|
+
The allowlist keeps the `*retire*`/`*provenance*` path patterns with **zero matches at this pin**
|
|
308
|
+
precisely so a future bump picks them up.
|
|
309
|
+
|
|
310
|
+
**Two verbatim upstream defects were copied as-is and recorded.** Upstream's `openai` entry lists
|
|
311
|
+
`gpt-4o` **twice** (the second occurrence is dropped, class `duplicate-id`), and its
|
|
312
|
+
`claude-sonnet-4.5` row carries the display name *"Claude Sonnet 4.6"*. The name is copied verbatim
|
|
313
|
+
because that is what "copied verbatim" means; correcting it silently would make the catalog
|
|
314
|
+
unfalsifiable against its own source.
|
|
315
|
+
|
|
316
|
+
## What was excluded, and why
|
|
317
|
+
|
|
318
|
+
`generated/rejections.json` carries all **722** rows. The counts below are generated from the ledger
|
|
319
|
+
and pinned by `catalog-integrity.test.ts` → *"PROVENANCE.md's exclusion table matches the ledger,
|
|
320
|
+
row for row"*, because a hand-typed count is the line that goes stale first and nobody notices.
|
|
321
|
+
|
|
322
|
+
| Class | Rows | What it means |
|
|
323
|
+
| --- | ---: | --- |
|
|
324
|
+
| `not-allowlisted` | 126 | an api-key provider upstream lists that Winter has not curated (WS-13 §1: presence is never inclusion). P6.5 cut this from 225 by admitting 107 through the allowlist and giving **every one of the remaining 126 its own hand-written reason** in `blocked` — a generic class row is not an exclusion anyone can review. That includes the 29 ids whose provider SHIPS as a reviewed overlay row: the class here is still `not-allowlisted` (the mapper stamps it from the id's upstream category, not from why it was kept out), but the REASON on each is the ships-as-an-overlay-row cross-reference `aihorde` and `cline` already carried. **The class name alone never says whether a provider is absent from the catalog** — the reason does |
|
|
325
|
+
| `executable-value` | 116 | functions, arrow functions, `Object.freeze(...)`, `new`, and other calls |
|
|
326
|
+
| `unresolved-reference` | 82 | an identifier whose declaration is outside the allowlist or was itself rejected — including the **three** models whose `unsupportedParams` could not be read (see below) |
|
|
327
|
+
| `dynamic-expression` | 51 | template literals with substitutions, property access, computed keys |
|
|
328
|
+
| `category-web-cookie` | 35 | browser-session transports, excluded categorically |
|
|
329
|
+
| `identity-header` | 30 | vendor client-identity headers — never imported |
|
|
330
|
+
| `category-oauth` | 25 | generic OAuth import is rejected; Winter's OAuth providers are Winter-owned rows |
|
|
331
|
+
| `unsupported-shape` | 47 | opaque runtime config, request defaults, malformed rows, and (P6.5) a `modelsUrl`/`responsesBaseUrl` carrying a query string or userinfo — R6-11 drops it rather than trimming, because a URL minus its query is a different request (`fireworks` is the one at this pin) |
|
|
332
|
+
| `credential-material` | 19 | OAuth client ids/secrets and literal anonymous API keys |
|
|
333
|
+
| `category-local-live-discovery` | 14 | local backends (Winter-owned, live-discovery only) plus the two image systems |
|
|
334
|
+
| `category-search` | 14 | not LLM providers |
|
|
335
|
+
| `category-no-auth` | 13 | reject by default (WS-13 §1) |
|
|
336
|
+
| `category-audio` | 12 | not worker-model providers |
|
|
337
|
+
| `unrepresentable-protocol` | 11 | Vertex's `targetFormat: "claude"` rows — see below |
|
|
338
|
+
| **`reviewed-normalization`** | 114 | **NOT an exclusion.** A row that DID ship, carrying a reviewed, recorded deviation from the pinned tree: the OpenRouter wire id, the Bedrock executor's protocol, the OpenAI and Vertex adapter overrides, the four Vertex partner statuses, and **one endpoint strip per admitted row** (WS-13b §2 — see "Endpoints diverge from upstream on purpose"), which is now the bulk of the class |
|
|
339
|
+
| `url-builder` | 4 | executable URL builders (WS-13 §13's security floor names this exactly) |
|
|
340
|
+
| `category-cloud-agent` | 3 | remote agent products |
|
|
341
|
+
| `category-upstream-proxy` | 2 | no proxy-of-proxy layer |
|
|
342
|
+
| `category-system` | 1 | `auto` is routing policy, which this layer bans |
|
|
343
|
+
| `duplicate-id` | 1 | upstream's second `gpt-4o` |
|
|
344
|
+
| `no-registry-entry` | 1 | `azure-openai` — catalogued upstream, with no backend entry |
|
|
345
|
+
| **`out-of-scope`** | 1 | **`gemini-3.1-flash-tts-preview`** — a TEXT-TO-SPEECH model. WS-13 §4 is a MUST: `tts` rows never feed the worker-model picker, and `scope` is per PROVIDER, so a `gemini` row cannot declare itself `tts` while its provider is `llm`. Excluded through the allowlist's reviewed `modelOverrides`, never a name heuristic — a heuristic would silently drop a future model whose id happened to match |
|
|
346
|
+
|
|
347
|
+
**`unrepresentable-protocol` is the interesting one.** Upstream's `vertex` entry lists eleven
|
|
348
|
+
`claude-*` models with `targetFormat: "claude"` — Claude models served over Vertex's endpoint in the
|
|
349
|
+
Anthropic dialect. `WinterProviderDescriptor.protocols` is per-**provider**, and `endpoints`
|
|
350
|
+
distinguishes only chat from responses within one family, so nothing in the schema can say "this
|
|
351
|
+
model speaks a different dialect from its provider". Emitting those rows anyway would resolve them
|
|
352
|
+
onto `winter.google-generate-content` and serialize the wrong dialect on the wire, so they are
|
|
353
|
+
refused with the reason recorded. Anthropic-on-Vertex is a recorded carry (R6-16), and a Winter row
|
|
354
|
+
for it belongs under a provider whose protocol matches.
|
|
355
|
+
|
|
356
|
+
## Adapter ids are read from the lanes, not guessed
|
|
357
|
+
|
|
358
|
+
Every cohort provider row's `adapterId` is the id constant the adapter that serves it actually
|
|
359
|
+
exports, read from the lane branches (`git show p6/lane-a:…/adapters/openai/*.ts`,
|
|
360
|
+
`git show p6/lane-b:…/adapters/google/*.ts`). Two were wrong, and a registry resolves an adapter **by
|
|
361
|
+
that id**, so both were live misroutes rather than cosmetic drift:
|
|
362
|
+
|
|
363
|
+
- **`vertex` named `winter.google-generate-content`** — the plain Gemini adapter. Lane B's Vertex
|
|
364
|
+
adapter is `winter.vertex-gemini` (`VERTEX_ADAPTER_ID`). A Vertex session would have been served by
|
|
365
|
+
the Gemini API adapter: no location-scoped URL, no ADC credential, and
|
|
366
|
+
`generativelanguage.googleapis.com` on the wire. `family` stays `google` deliberately — Vertex is a
|
|
367
|
+
*transport* over the same GenerateContent mapping (ruling R6-A), not a second dialect.
|
|
368
|
+
- **The twelve local rows named `winter.openai-chat-completions`.** They now name
|
|
369
|
+
`winter.local-openai`, the default `createLocalOpenAIAdapter` exports. The old value forced a host
|
|
370
|
+
to register the local adapter *under the chat adapter's id*, which **shadowed** the real Chat
|
|
371
|
+
Completions adapter for `openai`, `openrouter` and `deepseek`. Lane A can drop the `id:` override
|
|
372
|
+
from its wiring line.
|
|
373
|
+
|
|
374
|
+
- **`openai` derived `winter.openai-chat-completions`** in the upstream layer, from upstream's
|
|
375
|
+
provider-level `format: "openai"` — which names OmniRoute's own default execution path, not the
|
|
376
|
+
surface Winter drives. Six upstream openai rows carry `targetFormat: "openai-responses"` (the
|
|
377
|
+
`*-pro` and GPT-5.6 families are responses-ONLY), so the layer held responses-only models under a
|
|
378
|
+
Chat Completions adapter. The overlay row has said `winter.openai-responses` since the seed, and
|
|
379
|
+
that is precisely what hid it — the merged catalog was consistent while the layer was not. Found
|
|
380
|
+
by running the cross-layer gate over the STANDALONE layer, the same shadow class as Vertex.
|
|
381
|
+
|
|
382
|
+
`azure-openai` and `vertex` model rows are `experimental` per R6-16 (native cloud enters as
|
|
383
|
+
experimental, and both adapters are live). `bedrock` rows stay `candidate`: Lane N has not landed an
|
|
384
|
+
adapter, and R6-16's own demotion criterion covers that case. Nothing anywhere is `supported` —
|
|
385
|
+
that requires the behavioural corpus (WS-13 §13).
|
|
386
|
+
|
|
387
|
+
## Reviewed model-id corrections
|
|
388
|
+
|
|
389
|
+
`allowlist.json`'s `modelIdCorrections` is a hand-maintained map from an upstream model id to the id
|
|
390
|
+
the provider documents on its own wire. One entry exists: OpenRouter's auto-router, which upstream
|
|
391
|
+
lists as a bare `{ id: "auto" }` while OpenRouter documents `openrouter/auto`. Upstream's spelling
|
|
392
|
+
put `auto` on the wire from **both** resolution paths — step 1 (the seeded row's own `upstreamId`)
|
|
393
|
+
and the `allowUnlisted` pass-through, which strips a self-prefix — so correcting it at the source
|
|
394
|
+
fixes both. The upstream spelling survives as an **alias**, so a caller writing bare `auto` still
|
|
395
|
+
reaches the corrected wire id, and no row remains that would send a bare `auto`.
|
|
396
|
+
|
|
397
|
+
Every correction is written into `generated/rejections.json` with its reason. It is a *mechanical
|
|
398
|
+
normalization*, never a silent edit: a reader can diff the catalog against the pinned source and
|
|
399
|
+
find the one place they differ, with the justification attached.
|
|
400
|
+
|
|
401
|
+
## Why `google/gemini-2.5-pro` ships `efforts: []`
|
|
402
|
+
|
|
403
|
+
Not an omission. Gemini's `generateContent` surface has **no effort vocabulary at all** — thinking is
|
|
404
|
+
*budgeted* (`thinkingConfig.thinkingBudget`), not tiered into named levels. WS-13 §8.2 forbids
|
|
405
|
+
treating vocabularies as interchangeable, so borrowing OpenAI's `low`/`medium`/`high` here would
|
|
406
|
+
invent a control the endpoint does not accept, and the adapter would (correctly) reject the selection
|
|
407
|
+
before sending. The row **does** carry `summaryRequest`
|
|
408
|
+
(`thinkingConfig.includeThoughts`, `official-doc`, continuity report §6.1), so summaries are
|
|
409
|
+
requestable from session start per WS-13 §8.2's proactive-summaries rule; it is only the *effort
|
|
410
|
+
tiers* that do not exist. The overlay states this in a `$comment` on the `reasoning` block, but
|
|
411
|
+
`scripts/provider-catalog.ts` strips `$comment` keys from the merged catalog — which is why the
|
|
412
|
+
reason lives here, where a reader of the shipped artifact can find it.
|
|
413
|
+
|
|
414
|
+
## Blocked stays blocked
|
|
415
|
+
|
|
416
|
+
A provider reaches the catalog only through a reviewed edit to
|
|
417
|
+
`third_party/omniroute-provider-source/allowlist.json`. The check runs **both ways**: an id the
|
|
418
|
+
allowlist admits whose upstream category is not the api-key candidate pool **fails the whole run**
|
|
419
|
+
rather than being imported (WS-13 §3 step 7's blocked class transition), and so does a category
|
|
420
|
+
change on an already-admitted id. That is what makes "blocked → supported requires an explicit
|
|
421
|
+
reviewed allowlist change" a property of the pipeline instead of a promise.
|
|
422
|
+
|
|
423
|
+
## Pricing
|
|
424
|
+
|
|
425
|
+
`overlay/models.json` carries list prices for the cohort rows below, each with the vendor's own
|
|
426
|
+
pricing page as `sourceRef` and the observation instant. The set is pinned by name in
|
|
427
|
+
`src/extract/catalog-integrity.test.ts`, so a row gaining or losing a price is a deliberate edit:
|
|
428
|
+
|
|
429
|
+
| Model | Input | Output | Cache read | Cache write | Source |
|
|
430
|
+
| --- | ---: | ---: | ---: | ---: | --- |
|
|
431
|
+
| `anthropic/claude-fable-5-1` | 10 | 50 | 0.25 | 12.50 | docs.anthropic.com/en/docs/about-claude/pricing |
|
|
432
|
+
| `anthropic/claude-haiku-4-5-20251001` | 1.00 | 5.00 | 0.10 | 1.25 | claude.com/pricing |
|
|
433
|
+
| `anthropic/claude-opus-5` | 5.00 | 25.00 | 0.50 | 6.25 | claude.com/pricing |
|
|
434
|
+
| `anthropic/claude-sonnet-5` | 2.00 | 10.00 | 0.20 | 2.50 | claude.com/pricing |
|
|
435
|
+
| `google/gemini-2.5-pro` | 1.25 | 10.00 | 0.125 | — | ai.google.dev/gemini-api/docs/pricing |
|
|
436
|
+
| `google/gemini-3.5-flash-lite` | 0.30 | 2.50 | 0.03 | — | ai.google.dev/gemini-api/docs/pricing |
|
|
437
|
+
| `google/gemini-3.8-flash` | 0.75 | 3.75 | 0.075 | — | ai.google.dev/gemini-api/docs/pricing |
|
|
438
|
+
| `openai/gpt-4.1` | 2.00 | 8.00 | 0.50 | — | developers.openai.com/api/docs/pricing |
|
|
439
|
+
| `openai/gpt-5.6-luna` | 0.20 | 1.20 | 0.02 | — | developers.openai.com/api/docs/pricing |
|
|
440
|
+
| `openai/gpt-5.6-sol` | 4.00 | 20.00 | 0.40 | — | developers.openai.com/api/docs/pricing |
|
|
441
|
+
| `openai/gpt-5.6-terra` | 2.00 | 12.00 | 0.20 | — | developers.openai.com/api/docs/pricing |
|
|
442
|
+
| `openai/gpt-6-astra` | 10.00 | 50.00 | 1.00 | 12.50 | developers.openai.com/api/docs/pricing |
|
|
443
|
+
| `openai/o4-mini` | 1.10 | 4.40 | 0.275 | — | developers.openai.com/api/docs/pricing |
|
|
444
|
+
| `xai/grok-4.6` | 2.00 | 6.00 | 0.50 | — | docs.x.ai/docs/models |
|
|
445
|
+
|
|
446
|
+
USD per million tokens. This closes the seed's disclosed gap: `estimateCostUsd` returns
|
|
447
|
+
`costBasis: "list"` for these rows, and `maxBudgetUsd` is live for them.
|
|
448
|
+
|
|
449
|
+
**`ModelPricing` holds one rate per direction, and four disclosed limits follow from that.**
|
|
450
|
+
|
|
451
|
+
1. **Gemini 2.5 Pro is tiered by PROMPT SIZE** — prompts over 200k tokens bill at $2.50/$15.00/$0.25
|
|
452
|
+
— so the standard tier is recorded and cost is **under-reported for prompts above 200k**.
|
|
453
|
+
2. **Gemini 3.8 Flash is tiered in TIME** (P7a). Its page states $0.75/$3.75/$0.075 "through
|
|
454
|
+
December 31, 2026" and exactly double from January 1, 2027. The current rate is recorded, so from
|
|
455
|
+
that date the row under-reports by 2x until it is re-fetched. Its `-lite` sibling carries no such
|
|
456
|
+
schedule, and its row says so — the disclosure is each row's own evidence, never boilerplate.
|
|
457
|
+
3. **Claude Fable 5.1 has two cache-write durations** (P7a): $12.50/MTok for the 5-minute write and
|
|
458
|
+
$20/MTok for the 1-hour one. The 5m rate is recorded, matching every other Anthropic row here;
|
|
459
|
+
the 1h rate has no field. Its cache READ is the vendor's documented 0.025x exception ($0.25/MTok,
|
|
460
|
+
not the usual 0.1x), which is why that number looks out of line with its siblings.
|
|
461
|
+
4. **Batch (-50%), fast-mode, Flex/Priority and data-residency (1.1x) modifiers are never folded in.**
|
|
462
|
+
They are separate rate cards, and a blended number would be a price no invoice ever shows.
|
|
463
|
+
|
|
464
|
+
Every one of these is stated in the offending row's own `sourceRef`, so the caveat travels with the
|
|
465
|
+
data rather than living only here. **Gateway (`openrouter/*`), Azure-deployment, Bedrock and Vertex
|
|
466
|
+
rows are deliberately unpriced**: their prices are the reseller's, not the vendor list, and
|
|
467
|
+
attributing a vendor price to them would put a number on the wrong billing boundary — the exact
|
|
468
|
+
confusion WS-13 §8.3 keeps `openai/gpt-x` and `gateway/gpt-x` apart to avoid. Every upstream-derived
|
|
469
|
+
row is unpriced too, by construction: the extractor cannot emit `pricing` at all.
|
|
470
|
+
|
|
471
|
+
## `classifierEligible` is still absent everywhere
|
|
472
|
+
|
|
473
|
+
R6-14 sets it only after the safety corpus passes live. The catalog's answer to "may this model
|
|
474
|
+
serve as the permission classifier?" therefore remains **no** — Manual fallback, the fail-safe
|
|
475
|
+
direction, never a silent weakening.
|
|
476
|
+
|
|
477
|
+
## The helper-built entries: probed, then admitted or refused individually (round-1 finding I-1)
|
|
478
|
+
|
|
479
|
+
74 upstream `apikey` ids are built by a **helper call** (`buildOpenAiCompatibleRegistryEntry(...)`)
|
|
480
|
+
or a shared constant, which the literal extractor refuses to evaluate — so the pinned tree yields no
|
|
481
|
+
endpoint, auth, executor or model list for them, only the product-catalog identity. Round 1 ruled
|
|
482
|
+
that carrying all 74 on one generic reason was not good enough: an id is admitted from the **vendor's
|
|
483
|
+
own documentation** where one exists, and refused **individually, with the probe result**, where it
|
|
484
|
+
does not.
|
|
485
|
+
|
|
486
|
+
| outcome | n | what it means |
|
|
487
|
+
| --- | ---: | --- |
|
|
488
|
+
| **admitted** as reviewed overlay rows | **29** | a vendor documentation page was fetched and read on 2026-09-06 **and** it names a fixed API root |
|
|
489
|
+
| **admitted** as reviewed **per-tenant** rows (P7a) | **2** | `azure-ai`, `oci`: the endpoint is a per-tenant template, so the row ships **none** and the host supplies one (below) |
|
|
490
|
+
| refused — out of **scope** | 14 | image, video, embedding, reranking or web-extraction services. Not held pending a document: more evidence would not admit them |
|
|
491
|
+
| refused — **docs reached, no fixed endpoint** | 20 | a docs page answered 200 but states no base URL, or the host answered 403/530, or the vendor documents two hosts and no single base |
|
|
492
|
+
| refused — **no public fixed host at all** (enterprise) | 9 | the inference host is per-deployment or per-tenant by design, or no docs page could be reached |
|
|
493
|
+
|
|
494
|
+
For the 14 admitted from the probe list, **two independent sources agree on the base**: the vendor's
|
|
495
|
+
own documentation page, and the base upstream's product catalog states in its `apiHint` at the pin.
|
|
496
|
+
Neither was taken on the other's word, and no row was authored from the pinned tree alone —
|
|
497
|
+
hand-transcribing a helper call's arguments is precisely the extraction the literal extractor
|
|
498
|
+
refuses, so it is not a substitute for the document.
|
|
499
|
+
|
|
500
|
+
**Three traps, recorded because each looks like an admission until it is read.** `openference-api` is
|
|
501
|
+
not a provider: its documentation *is* the shipped `openference` row's documentation, on the same
|
|
502
|
+
base — one vendor wearing two ids. `hcnsec` and `helixmind` declare `format: "claude"` with a
|
|
503
|
+
`/v1/chat/completions` base, so the entry contradicts its own dialect and neither value can be
|
|
504
|
+
trusted; the vendor's doc is the tie-breaker and neither has a readable one. `muse-code` has no
|
|
505
|
+
vendor doc at all — its recorded `website` is a GitHub repository URL.
|
|
506
|
+
|
|
507
|
+
## The per-tenant rows: a row that ships NO endpoint (P7a, WS-13b §2/§10)
|
|
508
|
+
|
|
509
|
+
`azure-ai` and `oci` have real documented public APIs and were refused through P6.5 for one reason:
|
|
510
|
+
`defaultEndpoints.api` is immutable generated data (R6-11) and a per-tenant template
|
|
511
|
+
(`https://<resource>.services.ai.azure.com/openai/v1`,
|
|
512
|
+
`https://inference.generativeai.<region>.oci.oraclecloud.com/openai/v1`) is not an endpoint. The
|
|
513
|
+
round-1 ruling deferred them to "a dedicated host-supplied-endpoint adapter shape". **The user's
|
|
514
|
+
ruling of 2026-09-06 replaced that with a user-entered endpoint field**, and P7a ships it:
|
|
515
|
+
|
|
516
|
+
| field | what it means |
|
|
517
|
+
| --- | --- |
|
|
518
|
+
| `requiresUserEndpoint: true` | the row ships **no** `api` endpoint at all. The validator refuses one — presence, not shape: a plausible placeholder parses, validates, and would be copied into a connection profile and called |
|
|
519
|
+
| `endpointTemplate` | the documented shape, e.g. `https://<resource>.services.ai.azure.com/openai/v1`. **Never sent, never parsed as a URL** — it is documentation, and the only thing the runtime's typed `endpoint-required` refusal has to show a user |
|
|
520
|
+
|
|
521
|
+
At runtime the host's `connection.baseUrl` is **required** and is evaluated as a **USER** endpoint
|
|
522
|
+
(`endpointOrigin: "user"`), so no privileged header ever rides it (WS-13 §5 / R6-L). Absent, the
|
|
523
|
+
session refuses before a request exists rather than falling back to the shared adapter's vendor
|
|
524
|
+
default — which, for a row on `winter.openai-chat-completions`, would have meant this provider's
|
|
525
|
+
credential on the wire to `api.openai.com`.
|
|
526
|
+
|
|
527
|
+
**Neither row is authored from a fetched page.** Both cite `tier: "spec-ruling"` — the user ruling
|
|
528
|
+
admits the *path*, and the template is transcribed verbatim from the id's own P6.5 ledger entry.
|
|
529
|
+
This repository has not read `learn.microsoft.com/azure/ai-foundry` or
|
|
530
|
+
`oracle.com/artificial-intelligence/generative-ai` for content. Promotion is two-key as everywhere
|
|
531
|
+
else: the fetched page (upgrading the citation to `fetched-document`) **and** a live-gate pass
|
|
532
|
+
against a real tenant.
|
|
533
|
+
|
|
534
|
+
**No model rows, and `modelDiscovery: "none"`.** A per-tenant surface serves whatever deployments the
|
|
535
|
+
operator created; no document read here enumerates them, and seeding rows would be a claim about
|
|
536
|
+
somebody else's tenant. A host reaches models with `allowUnlisted` — both rows are
|
|
537
|
+
`liveCatalogAuthority: "unknown"`, which is the door R6-F opens.
|
|
538
|
+
|
|
539
|
+
## Dialect siblings, and the two keyless rows (P6.5, R6b-5 / WS-13b §8.4)
|
|
540
|
+
|
|
541
|
+
A provider row carries exactly one `adapterId`, so a vendor that documents **two wire dialects at two
|
|
542
|
+
base URLs** is **two rows** — each with its own endpoint, its own model keys and the dialect in its
|
|
543
|
+
`displayName`. Four pairs ship:
|
|
544
|
+
|
|
545
|
+
| OpenAI dialect | Anthropic dialect | Anthropic base URL | Where the pair comes from |
|
|
546
|
+
| --- | --- | --- | --- |
|
|
547
|
+
| `deepseek` (extracted) | `deepseek-anthropic` (overlay) | `https://api.deepseek.com/anthropic` | vendor guide, retrieved 2026-09-06 |
|
|
548
|
+
| `zai` (overlay) | `zai-anthropic` (**extracted**) | `https://api.z.ai/api/anthropic` | vendor docs for both halves, retrieved 2026-09-06 |
|
|
549
|
+
| `moonshot` (overlay, token) | `kimi-coding` (overlay, **subscription**) | `https://api.kimi.com/coding` | Kimi Code docs, retrieved 2026-09-06 |
|
|
550
|
+
| `minimax` (extracted) | `minimax-anthropic` (overlay) | `https://api.minimax.io/anthropic` | vendor Anthropic-SDK reference, retrieved 2026-09-06 |
|
|
551
|
+
|
|
552
|
+
The `zai` pair is the one worth reading twice: **upstream's own `zai` entry is the ANTHROPIC one**
|
|
553
|
+
(`format: "claude"` at `api.z.ai/api/anthropic/v1/messages`, which z.ai's Claude-client doc confirms
|
|
554
|
+
verbatim), so the allowlist admits that id under the `zai-anthropic` **`winterId`** — the same rename
|
|
555
|
+
door `gemini` → `google` already uses — and the OpenAI half is the reviewed overlay row. It is also
|
|
556
|
+
why `displayNameOverride` exists: upstream's product catalog has one name per vendor ("Z.AI"), and two
|
|
557
|
+
rows reading "Z.AI" are two rows a user cannot choose between. The override is a reviewed allowlist
|
|
558
|
+
edit, recorded in the ledger like every other normalization, and it is not a licence to rename
|
|
559
|
+
providers for taste.
|
|
560
|
+
|
|
561
|
+
**The Anthropic adapter is multi-provider in fact, and that was measured rather than assumed.**
|
|
562
|
+
`runtime/src/provider/catalog-endpoint-shape.test.ts` drives two sibling rows through the real
|
|
563
|
+
catalog-resolved adapter against a loopback fake; each reaches its **own** `<root>/v1/messages`.
|
|
564
|
+
There is no `providerId === "anthropic"` guard anywhere in `adapters/anthropic/messages.ts`.
|
|
565
|
+
|
|
566
|
+
**Subscription rows are a billing fact, not a label.** `kimi-coding` and `clinepass` carry
|
|
567
|
+
`pricingBasis: "subscription"`, so `priceUsage` returns nothing for them and a session on either
|
|
568
|
+
reports no `total_cost_usd`. `clinepass` shares an endpoint **and a key** with the token-priced
|
|
569
|
+
`cline` row and is still a separate row, because the basis is per row and that is the whole mechanism.
|
|
570
|
+
|
|
571
|
+
**The two keyless rows carry no credential, and `aihorde` is the reason to say so explicitly.**
|
|
572
|
+
AI Horde documents an anonymous default key. **It is not in this repository** — not in the catalog,
|
|
573
|
+
not in an adapter, not in a fixture. The row records only that a documented anonymous default
|
|
574
|
+
*exists* and cites the page that names it; supplying it (or, better, a registered key, which buys
|
|
575
|
+
queue priority) is the host's act through the ordinary credential path, which is why its `authKinds`
|
|
576
|
+
is `api-key` rather than a keyless kind. `catalog-integrity.test.ts` asserts the literal is absent by
|
|
577
|
+
name. `uncloseai` needs no credential at all and carries `authKinds: ["custom"]`: `local-none` is
|
|
578
|
+
reserved for a **local installation** (the twelve WS-13 §12 rows) and would make `connectionFrom`
|
|
579
|
+
stamp `local: true` on a public https host.
|
|
580
|
+
|
|
581
|
+
## Winter-owned rows
|
|
582
|
+
|
|
583
|
+
`codex-oauth` (D11) and the twelve local OpenAI-compatible ids (WS-13 §12) carry
|
|
584
|
+
`upstream.project: "winter"` and an **empty** commit: no extraction produced them, and the emptiness
|
|
585
|
+
is the honest marker. Local inventories are machine-specific, so `modelDiscovery: "local"` and
|
|
586
|
+
`liveCatalogAuthority: "unknown"` on every one, and live discovery is the real catalog.
|
|
587
|
+
`azure-openai` is the in-between case: it *is* catalogued upstream, so it keeps
|
|
588
|
+
`project: "OmniRoute"` and the real commit, but its only `sourcePath` is a product-catalog file and
|
|
589
|
+
every endpoint and auth fact on it is Winter-authored.
|
|
590
|
+
|
|
591
|
+
## Secrets
|
|
592
|
+
|
|
593
|
+
`validateCatalog` fails the build on any credential-shaped field name or value anywhere in the
|
|
594
|
+
document (`scanForSecrets`), and the extractor runs the **same** scan over the raw upstream literals
|
|
595
|
+
*before* mapping — a credential that never enters the extraction cannot leak from it, including into
|
|
596
|
+
a debug dump no output-side scan would ever see. Field names carrying credential or identity
|
|
597
|
+
material (`oauth`, `anonymousApiKey`, `headers`, `extraHeaders`, `defaultHeaders`) are rejected on
|
|
598
|
+
sight, whatever their shape. WS-13 §6 is categorical: descriptors never contain secrets.
|