`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.
"defineTool compiles an object root, so the annotation is a bare
TypedDict class name that opens nothing" is a false universal:
parameterSchemaSpecToJsonSchema compiles an OPEN object root, so an
empty parameter table and one with unrepresentable field names both
degrade to dict[str, Any], which opens one bracket. The conclusion the
sentence carries is unaffected -- 1 or 2 against a 182 cap -- so say
"a bare TypedDict class name or dict[str, Any], neither of which
carries a chain", in the JSDoc and the test comment that copied it.
pyScalar's docstring said the subscript tool-name comment quotes
"through the same call". It quotes through its own JSON.stringify call
site in renderToolsSdkPy and never reaches pyScalar, which only takes
const/enum scalars. Same function, different call site.
The mode-'both' test attributed assembly.tools to the public schemas().
That projection is wireSchemas, wired at ctx.systemPrompt.tools.
"the two code points CPython refuses" counted classes: NUL is one code
point, unpaired surrogates are the whole 2,048-wide D800-DFFF block. Say
kinds, in both the docstring and the test comment that mirrors it, and
restore the "odd" qualifier the test comment dropped -- an even trailing
backslash run does not eat the closing quote.
The soft-keyword test title still said "only special in statement
position", which the previous commit's own three-way split contradicts
for `case`: `case_block` is a clause head inside a `match` statement, not
a statement.
Add the case the subscript tool-name path lacked. A lone surrogate is
reachable in a name through JSON.parse of MCP wire JSON, and that path
has no UNPRINTABLE / LONE_SURROGATE fallback -- only the same ES2019
well-formed stringification the Literal path leans on.
The 182 the cap is chosen against had no direct case: the existing tests
cover the root chain and the TypedDict field, both of which start one
bracket lower. An array-rooted parameters schema reaches it from a plain
ToolSdkSchema literal, no raw register() needed. Exactly 180 arrays over
a const scalar is the worst case itself -- the root frame starts at
listDepth 0, so every list[ still emits and the innermost Literal[ is
reached rather than degraded; one deeper is where the item degrades.
Name the subscript tool-name comment in pyScalar's docstring: it quotes
through the same JSON.stringify call and inherits the same escapes and
the same pass-throughs.
pyScalar's docstring named only the two code points CPython refuses
anywhere in source. A bare quote, a trailing odd backslash, and a bare
LF/CR break the Literal line just as fatally, and JSON.stringify is what
covers those too. The argument also leaned on an unstated coincidence:
every escape JSON.stringify can emit is a Python escape for the same
character, which is why the emitted text both parses and decodes back to
the declared value. Say both, and assert the second class.
"statement head" does not describe `case`, whose clause block is not a
statement. Split the positions three ways.
Add the mode 'both' by python assembly, pinning the mode-by-language
matrix rather than leaving it to the shared code path.
A documentation rescan on master moved this README's Testing section out
of the package. This branch was still editing that section, so the
rebase asked which structure wins; master's does, and the branch keeps
only the Catalog resolution section its own change adds.
Re-records the pair fingerprint against the merged text.
pi-ai resolves a request's apiKey override only through a provider that
declares an api-key method: resolveProviderAuth short-circuits to that
method when the override is present, and otherwise falls through to the
credential store and then to ambient discovery. A provider with no
api-key method at all therefore resolves to nothing, and the request
fails with "Provider is not configured" before any network I/O.
Two routes hit that. openai-codex ships OAuth alone, so moving off the
/compat dispatch broke a profile that names a key for it — the old path
handed the token straight to the provider. And a catalog route naming an
api was being rebuilt with the harness's own auth, so `openai: {api:
openai-completions}` stopped reading OPENAI_API_KEY, contradicting the
documented promise that omitting a credential keeps provider-native
discovery.
Auth is now one decision for both constructions. A catalog route keeps
its installed provider's auth, through an api override too: which
environment a provider reads belongs to the provider, not to the wire
format its models speak. A catalog provider with no api-key method gets
the harness method beside its own, but only when the profile names a
credential — a keyless codex profile keeps the honest refusal, since
this adapter holds no OAuth store to resolve through.
Materialization now spreads the installed entry instead of enumerating
the result, so a Model field this package does not model survives a
pi-ai upgrade; headers went missing from an nvidia route exactly that
way once already. providerInfo reports the configured displayName, which
also joins the registration facts so a rename re-registers rather than
leaving the old label in every selector. A refused registration swap
gets its own diagnostic naming the route, matching the directory swap
beside it.
The README documented endpoint interrogation this layer does not
implement, and still described unknown providers as kept-last-good after
they became legal declarations refused at the write point. The Agent
Note claimed per-model reasoning configurability the schema never had,
required capacities the route now defaults, and stated an apiKey
override that short-circuits unconditionally.
The adapter omits the seam's reasoning field whenever a model carries no
reasoning metadata, which is the model's own property and says nothing
about where the model came from. Both the JSDoc and the Agent Note read
as though only hand-declared models were meant, so a reader would infer
that the 251 installed-catalog models pi-ai marks as non-reasoning still
offer their single off level. They do not, and that is the point: a
picker holding only off misrepresents a provider that thinks by default,
because off dispatches the same bytes as naming no effort at all.
Behavior is unchanged; only the prose that describes it was narrower
than the contract. adapter.spec.ts already pins the catalog case through
openai/gpt-4.1 and catalog.spec.ts pins the hand-declared one.
Three defects surfaced while driving the Models page.
A hand-declared model needed an explicit contextWindow and maxTokens,
but a provider listing usually returns ids and nothing else — so the
page happily wrote a profile the adapter then rejected, which took the
whole namespace down silently. Capacities now fall back to the route's
`defaultContextWindow` (262,144) and `defaultMaxTokens` (32,768). Both
are guesses by construction, which is why they are route fields a
deployment corrects once rather than constants buried in the adapter;
the fallback sizes the model and never becomes a per-request cap.
That silent failure was the second defect. A schema-valid profile the
adapter could not serve was stored and only rejected later, disabling
every route in the namespace with nothing said. `dsh-settings` gains an
optional `validate` on registration — a check for what a schema cannot
express — and `llm-pi-ai` refuses an unserviceable section at the write
that produced it. A stored section that fails keeps the namespace's last
good value, as a schema failure already did, so an externally edited
document still cannot strand the owner. The plugin's own last-good
fallback goes with it: nothing reaching it can fail any more.
Third, a model with no reasoning metadata advertised the single level
`off`, which pi-ai translates to *omitting* the reasoning option — the
same request naming no effort produces. Selecting it disabled nothing,
so a provider whose default is to think kept thinking with `off` shown
as selected. Such a model now reports no reasoning capability at all,
which is the seam's way of saying the control is unavailable, and the
per-model `reasoning` flag is gone: without a thinkingLevelMap to spell
levels it could only invent them.
The protocol table narrows to the three a hand-declared route reaches
today, most-reached first so a surface offering a choice defaults to the
one gateways actually speak.
Review found four defects in the declared-provider work.
`PiAiAdapter` reused one `Models` collection and mutated it whenever the
configuration changed. `Models.streamSimple()` resolves its provider
lazily — when the stream is first consumed, which is after the adapter
awaits the route's credential — so a configuration change landing in
that window let an in-flight request finish under a configuration it
never resolved against, or fail on a provider that no longer existed.
Each resolution now produces an immutable snapshot and every operation
captures one before its first await, which is what makes the seam's
per-step freeze (`llm.prepareCall()`) hold end to end: switching models
mid-reply takes effect on the next step, never inside the one in flight.
`defaultMaxTokens` was materialized from the catalog's `Model.maxTokens`.
The two answer different questions: pi-ai requires that field as the
model's output capability, while the seam's is a cap the deployment
chose to send on requests naming none, so every request had started
carrying a number nobody picked. Only an explicitly configured cap
reaches the seam now.
The configurable-provider directory was refreshed by disposing its
registration and making a new one. A candidate set the registry refuses
— a profile keyed `deepseek-official`, which llm-deepseek declares —
left the whole directory withdrawn and the Models page empty, silently,
because the settings callback contains the failure. The seam's
registration handle now carries `replace()` with the same
validate-first atomicity `registerAdapter` has.
The protocol table offered every pi-ai streaming API, including four
whose authentication a profile cannot express: Bedrock signs with SigV4
over AWS credentials and a region, Vertex needs a project, a location,
and ADC, Azure needs provider environment plus an api-version, and Codex
uses OAuth. Offering them handed back routes that cannot authenticate.
Catalog routes still reach them through their own provider.
A pi-ai route had to name an installed catalog provider, served that
catalog's models verbatim, and could override only the endpoint. An
OpenAI-compatible gateway, a self-hosted server, or a model newer than
the pinned pi-ai release was therefore unreachable, and a stale context
window could not be corrected without upgrading the package.
A route is now a declaration whose defaults come from the installed
catalog. `catalog.ts` merges that catalog under the profile's own model
entries, `provider.ts` builds the pi-ai Provider (reusing the catalog
provider when the route keeps its protocol, so implementations this
package cannot reconstruct keep working), and the adapter serves every
operation from one `createModels()` collection. That also retires the
`@earendil-works/pi-ai/compat` import, which pi-ai documents as a
temporary entry point it deletes with its ModelManager migration.
Credentials stay on the harness seam: the resolved key rides the request
as pi-ai's highest-priority auth override, so `Models` holds no
credential store and a named-but-missing reference still fails loud
instead of falling back to an unrelated ambient key.
A model's configured maxTokens now reaches the seam as defaultMaxTokens.
trim and escape commute for every input, so the new whitespace test does
not pin their order: UNPRINTABLE and LONE_SURROGATE are disjoint from the
set trim() strips, and both escapes emit plain non-whitespace ASCII,
leaving the leading and trailing whitespace runs byte-identical. State that instead of the false causal clause.
pyScalar's Literal path escapes nothing itself -- JSON.stringify is what
keeps it parseable, covering NUL and, under ES2019 well-formed
stringification, unpaired surrogates. Record the dependency and turn it
into a checked invariant. Pin the docstring emission site for a lone
surrogate too, mirroring the NUL case.
Two docstring corrections: describe's caller enumeration omitted the
synthetic { description } wrapper docLines builds, and "special in
statement position" does not describe `_`, which is special in a match
pattern. Both keep the conclusion they support.
Note which of the two table guards fires depends on the entry point.
The hero's backdrop ellipse is sized 1051/776 of the hero box so its blur
scales with the input card, which means it reaches past the column whenever
the column is narrower than the glow. `[data-conversation-scroll]` declared
only `overflow-y: auto`, and a box that scrolls in one axis computes the
other axis's initial `visible` to `auto` — so that bleed came back as a real
horizontal scrollbar, 24–95px of range across ordinary laptop widths.
Declare `overflow-x: hidden` on the column instead of leaving the second axis
to be derived. Clipping is unchanged (the box already clipped both axes); the
declaration withdraws only the bar and the user gesture.
U+00AD is 0xAD, so "Cf cannot be addressed by \xNN" was false for the
first example in its own list. The real boundary is the category: one
\xNN form covers Cc exactly, and escaping the single addressable Cf
member would leave a rule that is neither category- nor
addressability-shaped.
A lone surrogate is the NUL case rather than the invisible-character
case -- Python source must be UTF-8-encodable, and compile() raises
UnicodeEncodeError for one in a string literal or a # comment alike
(measured on 3.9). JSON.parse on a wire "\ud800" escape produces them,
so escape them as \uNNNN; the regex's u flag keeps well-formed astral
pairs intact.
Pin the whitespace-plus-surviving-control boundary, which also pins
trim-after-escape.
Unicode Cc is U+0000-U+001F plus U+007F-U+009F, and no C1 code point is
ECMAScript whitespace, so U+0080-U+009F all survived the collapse and
reached the docstring raw and invisible -- the gap the previous commit
closed for NEL alone. \xNN addresses the whole block, which is the same
reason the set stops at Cc, so widen the class to U+009F and pin
U+009B/U+009C/U+009F.
Windows-1252 bytes 0x80-0x9F decoded as Latin-1 produce exactly these.
Also: required TypedDict fields share the optional fields' listDepth
start, and a description of whitespace plus a surviving control
character is not absent.
The TTFT and tok/s readings divide by measured wall time, so they are not
reproducible: the same replayed scenario yielded 69 and 70 tok/s on
consecutive local runs, and a 3 ms replayed stream reads 26333 tok/s. Baking
those into committed goldens made the Web lane flaky by construction, and the
goldens for the readings themselves were never refreshed.
Three fixes, then a refresh:
The footer's decorative dots are `aria-hidden`, so the readings concatenated
into one accessible string — `Ran for 13sTTFT 0.2s12 tok/s`. That is a real
defect on its own (a reader hears one run-on instead of three facts) and it
also denied `{{duration}}` the word boundary it matches on, so even the
previously-stable `Ran for` duration started leaking raw. The separators now
carry flanking spaces.
`normalizeAria` gains `{{throughput}}` beside `{{duration}}`, and its duration
alternation accepts the stats line's compact `2m42s` as well as the
message-chrome template's `2m 42s` — the compact form had no pattern at all,
which is why `LLM 382m39s` survived the first refresh.
Refreshed 17 goldens. They also record that the stats line's `LLM` group now
renders at all: it folds assistant `timing`, which the live transcript adapter
only began attaching in this branch, so the group was previously dead in Chat.
Verified by running the lane in replay three times after the refresh: 41/41
files green each time, goldens untouched. Before this change two consecutive
runs disagreed on both the values and the failure count.
The merge staged the `--ours` consistency records before the merged prose was
re-recorded, so the committed hashes still describe the pre-merge content and
`verify-translation-pairing` rejects the pair. Re-record all three against
what actually merged.
The static-stub sentence over-generalized: `tools` and `ToolCallError`
ARE bound at run time, and a model reading "everything below is a stub"
could stop catching `ToolCallError`. State the boundary and pin both
halves in the fixed-instruction assertions.
UNPRINTABLE missed U+0085: it is Cc but not ECMAScript whitespace, so
it survived the collapse and reached the docstring raw and invisible.
Add it and scope the docstring to Cc, since the `\xNN` escape cannot
address the Cf formatting characters that pass through by design.
Record the backend PR's two runtime contracts -- inject only `tools`
and `ToolCallError`, and bind the assembly-time language to the
request -- in the Agent Note and at requireCodeRuntime.
- `relay` resolves its sender in `contextBody` like every other form. It was
the one shape whose marker could claim a form the body did not render: an
unreadable sender fell back inside the body while the row still said relay,
contradicting the contract this PR's own note states.
- `recall` requires the retained, omitted, and truncated fields. Completeness
is what the card exists to report, so a reference that cannot state it is
not a readable recall — showing the label alone presents a confident card
over unknown loss.
- The snapshot body states the supersession its producer framing line carries.
That line is the one part of the model-facing text no section contains, and
unlike an instruction context's `<system-reminder>` it states the form's own
semantics rather than wrapping content.
- `GoalMessageSource` is a discriminated pair, so `{ form: 'notice' }` without
its account no longer compiles. The guarantee this PR claims now holds at
that seam too, not only through `ContextFormed` on plugin sources.
- Goal and tool-goal summaries are bounded by a shared `boundContextSummary`,
which tool-tasks now uses as well. A goal objective is unbounded caller text
in exactly the way a task label is.
- The runtime snapshot interpolates once per request: agent-loop renders the
sections and joins them through `joinContextSections`.
- Every form's fallback branch is pinned, not only the notice one.