The 'adding a language is two table entries plus its renderer' checklist
predates the `CodeSdkLanguage` union and now contradicts the mechanism
sentence beside it: following it literally leaves the union untouched, which
is exactly the excess-property error that sentence promises. It is three
parallel edits, in the note's Decision and Consequences and in the
`SDK_RENDERERS` JSDoc.
Two guard descriptions still claimed work the compiler took over. The
Decision's 'the drift this guards against' now names the `satisfies` pins and
leaves the guards their reachable case, a mounted runtime reporting a language
neither table knows; `resolveFlavor`'s JSDoc drops 'keeps the table coupled to
SDK_RENDERERS' for the same reason. The Consequences said a half-added
language 'cannot arise' for the runtime guards — it can, one PR later at the
consumer's integration point, and never on this base; the claim is now about
timing rather than impossibility.
`SDK_RENDERERS` and `RUN_CODE_FLAVORS` had to stay in step by review alone:
the `Object.hasOwn` guards catch drift only once a runtime reporting the
half-added language exists, which is the one case that cannot arise. Both
tables are now `satisfies`-checked against a shared `CodeSdkLanguage` union,
so a missing or extra entry fails `typecheck`. The declared `Record<string, …>`
type stays, since `CodeRuntime.language` is an unconstrained `string`.
The code-runtime seam's own README row and `CodeRuntime.language` JSDoc still
named `'typescript'` as the sole well-known value; both now name `'python'`
too and say only `'typescript'` has a published backend.
`keeps a non-ASCII field name…` asserted that Code Mode omits the native
schemas so nothing else carries a dropped field's name, requiredness, and
type. That holds under `mode: 'code'` only; under `both` the native schemas
ship alongside the SDK, as the module header says. Earlier rounds swept
`py-types.ts` for this family and qualified five sites there; the spec was
never in scope, so this is the family's last unqualified member rather than
residue from those fixes.
The previous commit's `UNPRINTABLE` sentence said the raw-reach point for all
three characters is `pyScalar`'s `JSON.stringify`, and the test comment said
that route is the only one. Both are exclusive claims and both are false: the
subscript tool-name comment calls `JSON.stringify` itself, and a tool name
carrying NEL, LS, or PS always lands there, none of the three being
`XID_Continue`. `pyScalar`'s own docstring already recorded that inheritance,
so the file contradicted itself. Both sentences now name the two call sites.
The note's evaluation axis was introduced as "the `typing` names the block
spells", which excludes one of its own members (`A | B` is operator syntax)
and omitted PEP 585 builtin generics — `dict[str, Any]` and `list[…]` appear
in nearly every render and need 3.9. The axis is now "the names and syntax the
block would evaluate at definition time", enumerated 3.8 through 3.11.
The test title covered two of the three characters it asserts; NEL is NEXT
LINE, neither a line nor a paragraph separator.
Four non-blocking review suggestions, all prose plus one assertion.
`UNPRINTABLE`'s new sentence named three characters but only two raw-reach
points, leaving "and NEL?" open; it now says all three reach text through
`pyScalar`, and how the description path handles each. `pyScalar`'s
raw-pass-through list already covered NEL under "the C1 controls", and the
test now pins it alongside LS and PS, so the docstring's claim has a
mechanical check for every character it names.
The test title said "paragraph separators" for a pair whose first member is
LINE SEPARATOR. Two docstring paragraphs are reflowed to the file's ~80
columns after the earlier inserts left short lines.
The note's CPython-floor obligation gains a second axis: the `typing` names
the block spells (`TypedDict` 3.8, `NotRequired` 3.11, `A | B` annotations
3.10) are definition-time evaluation floors, not parse floors, so the floor PR
does not read "parseable on the supported range" as "executable on it".
Review read `JSON.stringify`'s raw pass-through of U+0085/U+2028/U+2029 as a
parse hazard: an LS in a `Literal[...]` value or in a `# tools["..."]` comment
would end the physical line and take the SDK block down. Measured on CPython
3.9.6 (Unicode 13.0) and 3.12.13 (15.0): all three are accepted in both a
string literal and a `#` comment, value round-tripping, and only LF and CR
terminate either. The set is the tokenizer's, not `str.splitlines()`'.
Both existing claims were accurate, so nothing changes behaviorally. Name the
distinction where it was assumed: `UNPRINTABLE`'s terminator sentence now says
which set it means, and `pyScalar`'s raw-pass-through list, previously "DEL and
the C1 controls", now also names LS/PS, which are neither. A test pins the raw
form for `const` and `enum` so escaping them later cannot land as a silent
divergence from the TypeScript flavor.
The claimed-message refactor changed Inbox.claim(target, turn) and added
InboxNotifications.claimed, so the cordis_inspect tool output embedded in
the cordis-inspect-jsdoc replay fixture now carries the new declarations.
The `mode: 'code'` qualification landed on the module header and
`isBareIdentifier` but not on the other three sites the same reviews
enumerated. `UNPRINTABLE`'s "only declaration of the tools", the open-object
comment's "only signal that extra keys are accepted", and the docstring
comment's "only description of what a tool does" are each false under
`mode: 'both'`, where the native schemas ship alongside the SDK.
Widen the note's predicate-path sentence past head and last position: a
character added to `XID_Continue` passes `IDENTIFIER`'s trailing quantifier
anywhere after the head, the middle of a name included.
Record the ƛ test's table provenance. U+A7DC and the U+019B mapping to it
both arrive in Unicode 16.0, and the engines floor sits exactly there:
Node 22.19.0 reports Unicode 16.0 (ICU 77.1) and produces the mapping.
Move the claimed-message notification loop out of the loop's pre-step
into Inbox.claim(target, turn), so the step-boundary operation publishes
its own claimed notifications like insertions and discards do.
`camelCase`'s `toUpperCase()` is a fourth reader of the engine's Unicode
tables, on a table distinct from XID membership and with a wider window: a
tool named U+019B passes `isBareIdentifier` and compiles as `async def` on
CPython 3.9.6, but Node maps the head to U+A7DC and the declared
`class Args` fails there with `invalid non-printable character`. Record it
alongside the three XID read points in the renderer docs and in the note's
CPython-floor obligation, and pin the derivation with a test.
Correct three over-quantified sentences: a `camelCase`-derived class name is
evaluated for every tool but only reaches emitted text when some object shape
in the schema declares a `TypedDict`. Attribute the `str.isidentifier()`
equivalence to `IDENTIFIER` rather than to the predicate, which is
deliberately stricter, and restore the antecedent the mode qualification
dropped.
The predicate is not the only reader of the engine's XID tables. camelCase
reads them through its split set and its head test, and the class name it
derives is emitted for EVERY tool -- including one the predicate rejected,
whose TypedDict is still declared and named. A tool named `zz-` + U+1E4D0
never reaches the skew in the predicate, since the `-` rejects it outright,
yet still emits `class Zz<U+1E4D0>xArgs`, which CPython 3.9.6 refuses the
same way. A backend PR executing "pin the predicate against tables for the
floor" literally would leave that path open, so the note and the docstring
now name all three read points.
Two corrections in the same paragraph. The failing direction is a character
added to XID_Start OR XID_Continue -- one added only to the latter passes
the trailing `\p{XID_Continue}*` in a tail position and fails identically.
And the safe direction routes a name to the subscript/`dict[str, Any]`
path: a rejected FIELD name degrades its whole enclosing object rather than
just itself, which the predicate's opening paragraph already said.
Also qualify the module header's "ONLY source" claim, which holds under
`mode: 'code'` but not `both`, where wireSchemas ships every native schema
alongside the SDK section; record the measured str.isidentifier()
equivalence (21 samples, zero divergence, Node 22.23.1 vs CPython 3.9.6)
where the versions it is relative to already live; and attribute the
`FInd` spelling in the ligature test to full case mapping rather than to
the NFKC step, which is the identity there.
Two tests. The fold-collision half of the childClassName fix: sibling joins
that are byte-distinct before NFKC and equal after, so `usedClassNames`
dedupes by raw bytes and the counter only sees the collision because the
join is normalized. And the argument-side oneOf-of-objects branch naming,
which reaches the same childClassName path the output side already pins.
A configuration surface never holds a stored secret — it edits a redacted
descriptor — so once a key is saved, the draft it sends carries the route and
the endpoint and no credential at all. The interrogation went out
unauthenticated and the endpoint's 401 came back as "check the API key",
pointing at the one thing that was fine.
A named route now supplies its own credential, resolved exactly as a request
to it would be. A key typed into the form still wins: it is the one under
test, and may be the replacement for the stored one that is failing.
Resolution is a callback the probe invokes past the catalog short-circuit and
the protocol check, so a route answered from the installed registry costs no
credential lookup — and cannot fail over a credential the question never
needed.
camelCase normalized `joined` and then prefixed, so the seam the `Tool`
prefix creates was never covered: `Tool` ends in `l`, a combining-mark
head composes with it, and a name headed by U+0301 was emitted as
`Tool` + U+0301 while CPython compiles `Too` + U+013A. childClassName
has the same shape -- both sides separately NFKC-stable, their join not:
a base ending in a Hangul L jamo or LV syllable composes with a V or T
jamo head. Beyond the declared-name/compiled-symbol mismatch, two
byte-distinct names can fold onto one, and usedClassNames dedupes by raw
bytes, so the collision counter never sees it. Normalize after the
prefix decision and at the join, before the cap. The remaining joins
need nothing: `Args`/`Output` and the digit suffix cannot compose
backwards.
Also record the Unicode-table skew. The predicate reads the engine's
tables (Node 22.23.1: 17.0) and the interpreter reads its own (CPython
3.9.6: 13.0.0), so an interpreter older than the engine takes a bare
name its tokenizer refuses -- U+1C89, U+10570, U+1E290 and U+1E4D0 are
accepted here and rejected there. The other direction only degrades a
legal name to subscript. Closing it needs the CPython floor, which the
backend PR owns; state the asymmetry in the docstring and make the
decision an explicit obligation in the note.
The pyScalar paragraph read as a universal over every beyond-safe-range
integral number, and three of its clauses have counterexamples inside
that very domain: String(2 ** 53) and String(1e20) are byte-identical to
BigInt's digits, so the "different integer or no integer literal at all"
split is not exhaustive, "the 16 digits" is 2 ** 60's instance count
rather than the mechanism (shortest round-trip is 1 to 17 significant
digits), and padded digits do name a held integer for 1e20. Say shortest
decimal string then padded to the exponent, give both counts, condition
the no-double-holds-it clause, and state the invariant that makes the
rule unconditional: where String is already exact the two agree, and
where it is not, BigInt is the exact one.
Also align one README.zh.md term: the same file already translates
"exotic names" as 特殊名称 in the SDK-section bullet.
The identifier test was ASCII-only, so an object with a `路径` field
degraded to dict[str, Any] -- dropping every sibling field's name,
requiredness and type, with no native schema behind it in Code Mode to
carry them. Python identifiers are `xid_start xid_continue*`, so match
that instead, and widen camelCase's split and head check to the same
sets (naming `_` explicitly in the split, since it is XID_Continue).
NFKC stability is a second and separate condition. CPython normalizes
identifiers at compile time while a JSON key is compared as written, so
a U+FB01 ligature key would be declared and reachable under its ASCII
expansion, a key the tool never accepts, and two keys that normalize
together would collapse
into one declaration. Those names take the subscript path. Generated
class names are normalized instead of rejected -- they are never matched
against a key. Astral characters can now reach the class-name cap, whose
slice counts UTF-16 code units, so drop a split surrogate half.
Also fix two comment claims. The note said one projection reads the
runtime twice per tool; the language-aware getters are installed on
run_code's own definition, so it is twice, both for that schema. And the
182-bracket site's reachability is an array reached from the root
through oneOf arms alone -- a union spine of any depth, not just one
root union; an object ancestor restarts the chain at the 181 site.
Master polished two Chinese sentences this branch also edits — an
expectation is now 陈旧 rather than 过期, and the package-root sentence
spells out 包(package). Taking master's wording alongside this branch's
own additions leaves the recorded pair fingerprints stale, so they are
re-recorded against the merged text.
llm.discoverModels was reachable from any declared trusted host. The
method takes a caller-supplied baseURL and makes the host issue a GET to
it, then reports the status or the parsed body — so on a LAN deployment
an anonymous caller had a probe for whatever the host can reach and the
browser cannot, plus a path that carries a draft credential. The
PRIVILEGED_METHODS doc already states the rule this broke: trustedHosts
is a DNS-rebinding fence, not authentication, so the configuration plane
stays loopback-same-origin. It is in that set now, asserted both against
the hand-built fence and over real HTTP beside the catalog reads that
deliberately stay reachable.
supportsDiscovery and listModelDiscoveryNamespaces are gone. The field
was required on the wire and read by nobody: its own contract said a
surface should offer the action "instead of naming an adapter family it
would have to hardcode", while the surface hardcodes llm-pi-ai in two
places and gates the button on whether there is anything to probe. Its
shape did not fit the second caller either — the create card has no row
to read a per-row field from. Keeping a required field alive for a
consumer that may never arrive costs every producer and fixture a value
nobody consults, which is exactly how the fixtures drifted. The registry
that fed it had no other production consumer, so registration and
disposal are now observed through the offer itself.
The Agent Note claimed the key is never logged, which the wire schema
beside it already contradicts, and predated both the provider field and
the catalog-answer path. The two new public types pointed at core.md
without a type-equiv block or manifest entry, so the generated service
catalog named documentation that did not exist.
Clicking "fetch available models" on a built-in provider went to the
network. That is the wrong source: pi-ai's registry is the authoritative
list for its own providers, and it carries the context windows and output
caps a `GET /models` listing does not disclose. Asking api.deepseek.com
what DeepSeek serves is both slower and worse, and against an endpoint
that answers a different shape it failed outright.
Interrogation is still keyed by settings namespace — the provider being
added has no route — but the request may now name the route it is
editing. An adapter that already describes that route answers from what
it knows, needs no endpoint at all, and never touches the network; only a
route the catalog does not describe reaches the wire, and one naming no
endpoint is told to set one or enter its models by hand.
`ConfigurableProviderView` gained `supportsDiscovery` so a surface offers
the action where a namespace can answer instead of hardcoding an adapter
family.
Three narrower corrections ride along. Discovery no longer claims Azure
or Codex: Azure authenticates with an `api-key` header and an
`api-version` query despite its OpenAI lineage, and Codex uses OAuth, so
both reported an authentication failure as a provider with no models.
Cancellation during the body read escaped as the raw abort reason rather
than a coded ABORTED. And the schema comment claiming the probe key is
never logged overstated it: the host neither stores nor returns it, but
it rides the client's outgoing envelope like every other secret-bearing
payload, and redacting that tap is a configuration-plane-wide change.
Once a pi-ai route became a declaration rather than a catalog lookup,
adding an OpenAI-compatible gateway meant knowing its model ids up
front. Most such endpoints publish that list at `GET /models`, but no
seam operation could ask: every one is keyed by a registered provider
route, and the provider being added has no route, no stored profile,
and no stored credential — the endpoint and key are values in a form.
Interrogation is therefore keyed by settings namespace, which a
configuration surface already holds from the configurable-provider
directory. `registerModelDiscovery` offers it per namespace,
`discoverModels` asks, and the request carries the draft itself. The
reply is candidates, not a catalog: every field but the id is optional
because most listings disclose nothing else, and adopting one is a
settings write like any other. Nothing here reads or writes settings or
credentials, so `settings.yaml` still decides what a route serves.
`llm.discoverModels` carries the same draft over the wire. Its apiKey is
the third and last payload a secret may ride, and it is never stored,
logged, or echoed; every refusal folds into `model-discovery-failed`,
naming the endpoint asked but never the credential offered.
The pi-ai side is a plain GET for OpenAI-compatible protocols only —
their listing shape is the one gateways, self-hosted servers, and the
official endpoints agree on. Others say so, sending the user to
hand-entry rather than reporting a guessed shape as an empty provider.
The reply is read under a four-megabyte ceiling held on the bytes
actually received, because the endpoint is a URL the user typed.
pyScalar's docstring attributed the rejection of a String-spelled
beyond-safe-range integer to "the Python runtime". No published backend
makes that call on this base. The fact that does not depend on one: the
padded digits name an integer no double holds, and passing it back would
have to cross the argument boundary as a JSON number. Say that, and say
why String rounds at all -- Number::toString is shortest round-trip, so
2 ** 60 emits the 16 digits that re-read to the same double and pads.
Mirror both in the test comment.
The note's Decision sentence said a test covers the flavor guard through
ctx.tools.schemas(). The test reads the definition's getter directly,
under a language absent from both tables; schemas() reaches the same
getter but has no assertion. Name what is read, and record that a
renderer-without-flavor language is drift this guards against rather
than an existing input -- the two key sets are identical today.
"Reachable only through a raw register() whose parameters is
array-rooted" was too narrow. A root oneOf reaches the same 182: the
union arm propagates listDepth unchanged because `A | B` opens no
bracket, so an array branch starts its chain at 0 exactly as an array
root does. Say "root opens an array chain -- rooted at the array, or at
an array branch of a root oneOf", in the JSDoc and the test comment,
and assert the union shape alongside the array-rooted one.
The note's Decision paragraph said the flavor guard is reached under
"a language that has a renderer but no flavor entry, and a test covers
it". The test uses ruby, absent from both tables, and the mechanism is
that schemas() reaches run_code's getters without passing
requireCodeRuntime -- so any language absent from the flavor table hits
it. State that instead.
The Consequences paragraph recorded the language-binding obligation as
two reads, assembly and execution. Within one projection there are more:
run_code's description and parameters getters each call
resolveFlavor(peekRuntime()) and schemaOf destructures both, so a reload
between them yields one schema whose halves name different languages.