readSourceConfigBestEffortFromContext collapsed three distinct
failures — parse error, $include resolution failure, any other read
error — into {} or a root-only config with zero recorded fact. A
corrupt config (or an update.channel living in an unresolvable
$include) silently flipped the update-channel decision and doctor-lint
input to defaults, indistinguishable from a missing file. The main
snapshot path logs an actionable message for the same conditions.
Root cause: 'best effort' legitimized the fallback value AND the
silence. Each degradation branch now warns once with the config path
and cause; the fallback values are unchanged.
Regression: unparseable config yields {} plus a recorded
'best-effort read ignored unparseable config' warning — fails pre-fix
(silent).
* fix(security): harden canonical bounded JSON body reading
* refactor(admin): use canonical bounded JSON body reader
* refactor(telegram): use canonical bounded JSON body reader
The assertion-safety baseline prune for extensions/telegram/src/miniapp/routes.ts (2 to 1) is explicitly approved.
* test(security): cover canonical JSON body migrations
* refactor(plugin-sdk): name response-first body profile
* fix(telegram): flush miniapp body-limit responses before close
Direct commit 4d64889290 pushed provider-provisioning.test.ts to 1091
lines, past the 1000-line max-lines cap, breaking check-lint-core-2 on
main. Move the replay/restart-adoption/serialization tests into
provider-provisioning.replay.test.ts (same suite setup via
service.test-support), matching the repo's split-not-suppress policy.
* fix(gateway): bind auth limits to ingress attribution
* fix(gateway): close remaining ingress auth gaps
* fix(gateway): carry attribution into new ingress paths
* fix(gateway): close ingress ownership gaps
* fix(gateway): complete proxy ingress hardening
* fix(gateway): stabilize managed Tailscale ingress
* fix(gateway): make Tailscale cleanup ownership-safe
Refuse reset-on-exit publication until Tailscale exposes an atomic owner-bound cleanup operation, and migrate legacy configs with Doctor.
* fix(gateway): finish ingress ownership repair
* fix(gateway): own managed Tailscale route lifetime
Run managed Serve and Funnel routes as foreground claims tied to the Gateway lifecycle. Retire named Service config through Doctor because Tailscale Services cannot run in foreground mode.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): align Tailscale consumers and build guards
Remove the retired named-service config from Telegram Mini App URL resolution and register the lifecycle worker as an explicit production entry.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): preserve retired Tailscale inputs
Keep Funnel enabled when removing an ignored named-Service setting and accept the legacy positive reset flag as a no-op now that managed routes always follow Gateway lifetime.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): preserve Tailscale route diagnostics
Prefer the actionable foreground CLI failure captured during timeout cleanup, and cover the original delayed-failure ordering.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): reconcile Tailscale ingress with main
Preserve current ingress ownership contracts after the rebase, retire the obsolete device-auth migration check, validate route-owner IPC, and move Tailscale auth coverage onto the managed listener.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): finish ingress rebase coverage
Unify the rebased net imports and let module-reset WebSocket tests prepare attribution through the same fresh module instance as the handler.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* test(gateway): align run-loop server fixture
---------
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Make the Sessions route and page share the managed session-list owner so startup hydration, reconnects, filters, and mutations cannot schedule duplicate raw roster requests. Preserve last-good rows and retire stale query epochs.
* fix(agents): preserve resolved agentId in model workspace resolution
resolveModelWorkspaceDir re-resolved a default agent instead of reusing
the agentId the caller already authorized, so any plugin call to
runtime.llm.complete with an explicit agentId threw
AgentSelectionRequiredError on multi-agent configs. Thread the known
agentId through resolveModel/resolveModelAsync into workspace
derivation so it is only re-derived when the caller has none.
Fixes#124822
* fix(agents): forward agentId through route-materialization re-resolution
The route-materialization resolver call in prepareSimpleCompletionModel
re-resolved the model without the caller's agentId, so a multi-agent
plugin completion that switches OpenAI routes still fell back to
resolveDefaultAgentId and threw. Forward params.agentId on this call
too, matching the first resolution.
Blacksmith's image tracks an older runner-images snapshot. Measured on a leased
box 2026-08-16, its toolcache holds Node 20.20.0, 22.22.0 and 24.13.0 while this
repo's engines floor is >=22.22.3 and >=24.15.0 -- short by three and two
patches. Every candidate is rejected, so all 306 of 306 sampled jobs fell
through to a nodejs.org download. GitHub-hosted runners carry 24.19.0 and
resolve from the toolcache in about a second, which is why only Blacksmith pays.
Normally that download is 2.6s (p99 3.3s), but ~46 jobs fetch the same 50 MB
simultaneously and the mirror throttles: three of 53 sampled runs had setup-node
medians of 44-93s with maxes to 139s, and because every job pays at once it
lands whole on the wall -- those runs went ~210s to 325s.
Keep the payload in the Actions cache, which Blacksmith serves from its
colocated backend. Measured on Blacksmith: cold 1605ms, warm 77ms.
Restores are prefix-keyed and the save carries the resolved patch. An exact key
would be worse than nothing: cache entries are immutable and an exact hit
suppresses the post-job save, so a floating `24.x` key would pin the first Node
it ever saw and, once the floor advanced past it, every job would restore the
rejected payload and re-download forever. Keying the save on the installed
version lets a newer resolve publish a new entry that later prefix restores
pick up.
A rejected payload is pruned before the replacement installs, because the entry
is saved wholesale and a leftover would ride along in every future save.
Windows keeps its existing path. Proven on Blacksmith across cold, warm, stale
and truncated-binary cases; both guards are mutation-checked.
This stays useful even if Blacksmith refreshes their image: the floor moves
independently of the snapshot, so the gap recurs. The image refresh is still
the better fix and is worth asking them for.
The cron model picker help and the automations docs now say what
operators otherwise learn by surprise on the bill: summaries, triage,
and classification jobs run well on a lighter model, and --fallbacks
covers escalation. Copy-only change; pnpm ui:i18n:verify green, no
baseline drift.