fix(workbench): bind production route executables from the artifact manifest, fail closed (#680) - #684
Conversation
`hooksFlightWorkerPath` lives in the composite-layout leaf so the hooks surface plan and the Workbench binding read the same artifact path. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…anifest Resolve the routed CLI bin, rendered script worker, owning compiled MCP server worker, and event wrapper plus runtime worker from agent-bundle.manifest.json before anything runs, instead of listing *-flight.mjs candidates and hopping to the next worker on a missing-route error. Preparation fails closed (AB8250/AB8251/AB8252) when the manifest cannot bind the route or the bound module lacks its preparation export, and a failure inside the bound worker never runs another executable. Fixes #680 Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
… fail-closed diagnostics Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
🦋 Changeset detectedLatest commit: c2675ae The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…e binding Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…he manifest eventRuntimeHosting gives the runtime to the first generated server in model order that targets the host; the model is ordered by server name and executables.mcpServers[] by id (mcp:<name>), so the first compiled row with a Flight worker reaching the host is that server. Drops the harness eventRuntimeServerId tie-breaker, which was global rather than per host, and the ambiguity failure it required. The dev-server integration test now runs a shared-runtime event route through a real artifact that also carries the standalone hooks worker. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…t route; AB8252 is the child's guard Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…he shared-runtime event Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
commit: |
|
Superseded by #692 after a symbol-by-symbol comparison against the expanded #680 acceptance criteria. #692 keeps selection in the leased parent and carries the exact manifest-owned execution/preparation binding into the child, includes real compiled multi-server and ineligible-host import sentinels, and avoids the new resolver abstraction prohibited by the issue scope. I am porting #684's unique execution-record runtime selection, shared hooks-worker path constant, preparation-export regression, and bilingual Workbench wording into #692 before its final review and gate. |
Fixes #680
What changes
packages/agent-bundle/src/dev/routes/route-invocation-production.tsno longer lists*-flight.mjscandidates (candidatesFor,workerFiles), guesses the event wrapper path (eventWrapperPath), or hops to the next worker when one fails withGenerated route must default-export(missingRouteWorkerError). Every production invocation now:agent-bundle.manifest.json(readArtifactManifest) — a root without a readable, canonical manifest isAB8250.route-invocation-executable.ts,resolveRouteExecutable), before any module is imported or any worker spawned:executables.bins[]row named afterapplication.name(the one routed CLI binnormalizeBinEntriesgenerates):path(bin) +worker; the route must be inroutes.cli.routes.executables.scripts[]row withrendered.routeId === routeId→worker.serverId→executables.mcpServers[]row,kind: 'compiled'→launch.worker.routes.events[].execution; with a host, theexecutables.hooks[]row (kind: 'event-route',routeId,host) is the wrapper. Worker:hooks/hooks-flight.mjs(must be afiles[]row) forruntime: 'standalone'; forshared, the first compiledmcpServers[]row with alaunch.workerwhosehostsinclude the host — which is exactlyeventRuntimeHosting's choice, becausenormalizeMcpServersorders the model by server name and the manifest sorts rows byid(mcp:<name>). A canonical (host-less) run takes the first such row of any host (it hosts the runtime for every host it reaches, and every hosting worker registers every event route).fallback: 'standalone'with no hosting server binds the standalone worker.AB8251) for a route the manifest does not compile, a hosted event with no wrapper row for that host, or a bin/script/server without a worker; fails closed (AB8252) for a bound bin/wrapper that does not export its preparation contract, and for a canonical submission of a route with compiled preflight should one reach the child (the service already refuses it asAB8255) — the old code fell through to the handler with raw input in the missing-wrapper and missing-export cases.renderCompiledtakes oneworkerPath; a failure inside it (handler throw, worker crash, missing-route error) propagates as the invocation's failure. There is no loop and no second candidate.hooks/hooks-flight.mjsis now named once (hooksFlightWorkerPathinadapters/composite-layout.ts) and used bybuild/entries.tsand the binding.The #604 manifest contract is unchanged: no key added or removed, no version bump; the binding only reads rows the parser already proves (
referencedPaths,requireLaunchFiles).Tests
packages/agent-bundle/tests/route-invocation-production.test.ts(new, unit pool; hand-built canonical manifest viaserializeArtifactManifest+ stand-in Flight workers that record when they start):AB8252, CLI surface on a route the routed CLI does not compilerenderProductionRoute: the bound worker'sGenerated route must default-exporterror is the failure and the siblingmcp/mcp-beta-flight.mjs/hooks/hooks-flight.mjsnever start; a handler failure propagates with no other executable run;AB8250/AB8251/AB8252are raised before any worker starts; adenypreflight from the bound wrapper returns without a worker, and a wrapper lackingprepareRouteInvocationisAB8252packages/agent-bundle/tests/route-invocation-dev-server.test.ts('enforces compiled preflight…'): the real compiled artifact now also carries a shared-runtime event route (tool/after) beside the standalonetool/before, so both the server worker andhooks/hooks-flight.mjsexist; the hosted invocation renders, which only the server worker can do — there is no fallback candidate any more.Docs / changeset
website/docs/{en,zh}/guide/development/workbench.mdx: new paragraph on manifest binding and the fail-closed codes.docs/diagnostics.md:AB8250–AB8255row updated (generated diagnostics page follows)..changeset/680-workbench-exact-executable-binding.md(patch).Deslop
Deslop: Claude Fable 5.1, 3 edits — replaced a type-predicate intersection with a plain
find, flattenedlet wrapper+ if/else intoeventWrapper, dropped the now-unusedROUTE_INVOCATION_COMPILED_ROUTE_UNAVAILABLE_CODEimport from the production module.Self-review
Reviewer:
gpt-5.6-sol-high(generalPurpose), two passes againstorigin/main.eventRuntimeServerId, buteventRuntimeHostingpicks an owner per host; a three-server artifact split across hosts could failAB8251where main's probing ran a valid worker.667d370d): owner is now the first compiledmcpServers[]row with a worker reaching the host, verified equal toeventRuntimeHostingvia the model/manifest sort orders; the ambiguity error and theeventRuntimeServerIdinput are gone. Unit matrix with three servers over two hosts added.667d370d): shared-runtime event route added to the compiled dev-server fixture that also carrieshooks/hooks-flight.mjs.AB8252, but the public API refuses it asAB8255.67ffdebf):AB8255documented as the service refusal,AB8252as the child's guard.Second pass: "All three findings are resolved. No remaining concrete merge risks found."
Local gate
Run on
c2675aeb(branch containsorigin/mainb435f7b9), Node 22.22.2: