Skip to content

Commit e26b3cd

Browse files
fix(diagnostics): reallocate event preflight codes to AB4850/AB4851
AB4840-AB4842 are owned by the CLI surface projections of #596 (PR #616); this branch's event-preflight export diagnostic moves to AB4850 and the required-provider declaration diagnostic to AB4851 so the ranges stay disjoint. Both codes are unused on main and on the #616 branch. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
1 parent 3311f04 commit e26b3cd

7 files changed

Lines changed: 26 additions & 26 deletions

File tree

‎.changeset/595-event-preflight-gates.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"agent-bundle": patch
33
---
44

5-
Allow an event route under `src/events/**` to declare a `preflight` gate — `export { default as preflight } from './<name>.js'`, a sync or async function that receives the frozen `{ canonical, host, signal, terminal }` context and returns `'execute'`, `{ outcome: 'continue' }`, or `{ outcome: 'deny', reason }` — which the generated hook entry runs on the canonical event before the rendered route runtime, React, or any application provider loads. `inspect`, `validate`, `build`, and `dev` report `AB4840` when `preflight` is declared inline, exported more than once, re-exported from a bare package or under a binding other than `default`, unresolvable, cyclic, or not a function, naming the route module and, once a re-export was found, its specifier. Allow an executed event route to declare the provider keys it requires (`config.providers: ['<key>', …]`) so only that subset resolves, in the existing deterministic key/source order with `processLifetime` seeded first; a route without a declaration still resolves every conventional provider, `[]` mounts `processLifetime` alone, and `AB4841` reports a malformed declaration, a duplicate key, the reserved `processLifetime`, or a key that matches no discovered `src/providers/*` module — unknown keys list the project's provider keys. Export `EventPreflight`, `EventPreflightContext`, `EventPreflightResult`, `validateEventPreflightResult`, and `eventFamilyAllowsPreflightDeny` from `agent-bundle`, `agent-bundle/api`, and `agent-bundle/routes`. Export the payload-free `EventTraceEvent` union, `createEventTracer`, and `installEventTraceObserver` for developer tooling. (#618)
5+
Allow an event route under `src/events/**` to declare a `preflight` gate — `export { default as preflight } from './<name>.js'`, a sync or async function that receives the frozen `{ canonical, host, signal, terminal }` context and returns `'execute'`, `{ outcome: 'continue' }`, or `{ outcome: 'deny', reason }` — which the generated hook entry runs on the canonical event before the rendered route runtime, React, or any application provider loads. `inspect`, `validate`, `build`, and `dev` report `AB4850` when `preflight` is declared inline, exported more than once, re-exported from a bare package or under a binding other than `default`, unresolvable, cyclic, or not a function, naming the route module and, once a re-export was found, its specifier. Allow an executed event route to declare the provider keys it requires (`config.providers: ['<key>', …]`) so only that subset resolves, in the existing deterministic key/source order with `processLifetime` seeded first; a route without a declaration still resolves every conventional provider, `[]` mounts `processLifetime` alone, and `AB4851` reports a malformed declaration, a duplicate key, the reserved `processLifetime`, or a key that matches no discovered `src/providers/*` module — unknown keys list the project's provider keys. Export `EventPreflight`, `EventPreflightContext`, `EventPreflightResult`, `validateEventPreflightResult`, and `eventFamilyAllowsPreflightDeny` from `agent-bundle`, `agent-bundle/api`, and `agent-bundle/routes`. Export the payload-free `EventTraceEvent` union, `createEventTracer`, and `installEventTraceObserver` for developer tooling. (#618)

‎docs/diagnostics.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ even when no error diagnostic was reported.
3030
| `AB4765`–`AB4766` | Artifact-hosted routed CLI: a target without the `cli` capability omits `bin/<name>.mjs`; a host-emitted file collides with it (see below). |
3131
| `AB477x` | MCP App view compilation (`AB4770`: compile error with file, line, column and the bundler message; `AB4771`: compile warning; `AB4772`: emitted-size advisory; see below). |
3232
| `AB490x`/`AB492x` | Conventional host components (#100 stage 2): rules `src/rules/*.mdc` (`AB4900`–`AB4908`) and commands `src/commands/*.md` (`AB4920`–`AB4928`), including per-host feature-set enforcement (`AB4907`/`AB4908`, `AB4927`/`AB4928`); see below. |
33-
| `AB48xx`/`AB494x` | Route graph, state, layout (`AB4830`–`AB4832`), generated route declarations outside the TypeScript program (`AB4834`), route render budgets (`AB4835`), tool task support (`AB4836`), a route module that value-imports a compiler-carrying framework entry (`AB4837`), a CLI route `inputSchema` reference the static resolver cannot follow (`AB4838`) or that cycles (`AB4839`), an event route's `preflight` gate export (`AB4840`), an event route's declared provider keys (`AB4841`), and provider conventions (see below). |
33+
| `AB48xx`/`AB494x` | Route graph, state, layout (`AB4830`–`AB4832`), generated route declarations outside the TypeScript program (`AB4834`), route render budgets (`AB4835`), tool task support (`AB4836`), a route module that value-imports a compiler-carrying framework entry (`AB4837`), a CLI route `inputSchema` reference the static resolver cannot follow (`AB4838`) or that cycles (`AB4839`), an event route's `preflight` gate export (`AB4850`), an event route's declared provider keys (`AB4851`), and provider conventions (see below). |
3434
| `AB5000` | General CLI and adapter failures (see below). |
3535
| `AB60xx` | Built-artifact validation, including schema documents and referenced files (`AB6005`: an emitted JavaScript module — a host-pack module or a package build `dist` bundle (`dist/bin/*.js`, the Flight workers, the `lib` entry), prebuilt payloads excepted — has an import that is neither a Node built-in nor a relative or `file:` specifier resolving to a listed regular file inside its tree, or a non-literal dynamic import; a `dist` finding names `dist/<path>`; `AB6011`/`AB6012`: a target's required pinned-schema document is missing or invalid; `AB6025`: a manifest-declared `logo` path is missing from the artifact or escapes the deploy tree; `AB6034`: emitted Skill Markdown has no instruction body; `AB6035`–`AB6038`: Agent Plugins portable validation, see below). |
3636
| `AB6200`–`AB6202` | Workbench artifact inspection over published epochs: `AB6200` the epoch does not validate or its provenance is inconsistent, `AB6201` an epoch reference could not be released, `AB6202` unsafe runtime metadata (see below). |
@@ -1221,7 +1221,7 @@ the route names, never a second event route. A `preflight` declared inline in
12211221
the route module (`export const preflight = …`, `export function preflight`)
12221222
is rejected too: evaluating the route module evaluates its rendering and
12231223
provider imports, the very cost the gate exists to avoid. Every rejected form
1224-
is `AB4840`, once per route on the route module; the route compiles without a
1224+
is `AB4850`, once per route on the route module; the route compiles without a
12251225
gate beside the error, and because the diagnostic is an error the build fails
12261226
instead of silently taking the expensive path.
12271227

@@ -1239,7 +1239,7 @@ the generated `AgentBundleProviders` declares; `processLifetime` is not one of
12391239
them and must not be declared. The declaration is judged when the route graph
12401240
compiles: a declaration that is not an array of string literals, a key listed
12411241
twice, the reserved `processLifetime`, or a key naming no discovered provider
1242-
module is `AB4841`, once per route with every defect in one message; a
1242+
module is `AB4851`, once per route with every defect in one message; a
12431243
declaration with any defect selects nothing, so the build fails rather than
12441244
resolving a provider set the author did not write.
12451245

@@ -1285,8 +1285,8 @@ resolving a provider set the author did not write.
12851285
| `AB4837` | error | A route module of any kind except an App — a `src/cli/**` command, a `src/scripts/**` script, a tool, resource, or prompt route of a generated server, an event route — a layout, or a provider, or a module one of them reaches through relative value imports, imports `agent-bundle`, `agent-bundle/api`, `agent-bundle/config`, `agent-bundle/eval`, `agent-bundle/rstest`, `agent-bundle/test`, or `agent-bundle/test/browser` as a value (a static import whose binding is read at run time, `import 'agent-bundle/api'`, `import('agent-bundle/api')` with a literal specifier, or a non-type re-export). Those entries carry the compiler, and the generated executable is self-contained (#387): the bundler would inline the compiler and fail on the framework's runtime-relative module references (`Module not found: Can't resolve '../events'`), or the artifact validator would reject the inlined compiler's non-literal dynamic imports with `AB6005` — either way naming a generated file instead of the route (#558). Judged statically when the route graph compiles, so `inspect`, `validate`, `build`, and `dev` all report it, once per module, naming the route and the helper the import lives in. `import type`, `type`-qualified specifiers, and imports used only in type positions are elided by the bundler and never reported; routes of a server that is not generated (`custom`/`command`/`remote`, or an `AB4800` conflict) or of a CLI that is not generated (`conventional`, or an `AB4801` conflict) are never bundled, so they are not judged; likewise a layout that no bundled rendered route composes through (a worker imports only the layouts its routes reach: the tool, resource, and prompt routes of a generated server, the rendered `.tsx` commands of a generated CLI, and rendered `.tsx` scripts), and a provider in a project whose only executables are plain `.ts` scripts, which are bundled from their own source and mount none. Spawn the framework instead of importing it: serve an MCP App from a routed command with `spawnServeApp` from `agent-bundle/serve-app-command`, which runs `agent-bundle serve-app` as a child process; keep other framework calls in host processes (`package.json` scripts, a hand-written `.mjs` run from the checkout). The bundle-safe entries stay allowed: `agent-bundle/app` (the browser MCP App client, a leaf with no Zod, Node, or compiler import), `agent-bundle/routes`, `agent-bundle/launch-env`, `agent-bundle/meta`, `agent-bundle/mcp-apps`, `agent-bundle/mcp-entry`, `agent-bundle/cli-entry`, `agent-bundle/terminal-capability`, and `agent-bundle/serve-app-command`. |
12861286
| `AB4838` | error | A CLI route's `inputSchema` references a binding the static resolver cannot follow. The message is `CLI route <path> inputSchema: <chain> <reason>.` — the chain is the reference path from `inputSchema`, each step `<binding>`, or `<binding> (<module>)` when it crosses into another module (`inputSchema -> statusInputSchema (src/lib/protocol-schemas.ts) -> requestStatusSchema -> requestStatuses`), and the reason names the boundary: a specifier that `is not a relative module path`, one that `resolves outside the project` or `does not resolve to a module inside the project` (missing or unreadable), a target module that does not declare a top-level `export const <name>`, a binding that is not a top-level `const` (`let`/`var`, destructuring, a function, a class, a default or namespace import — the message says what it is), an identifier that `is neither a top-level const in this module nor a named import from a relative module`, or a dynamic initializer — one that is neither a method chain, an object or array literal, nor a static literal (`whose initializer is a call expression`, `a function expression`, `a template literal with substitutions`). Reported on the route module; the recovery names the supported forms — relative imports inside the project, `export const`, alias chains — then says to inspect again. Only CLI routes raise it, because only there the static contract is load-bearing: an MCP, script, or event route whose schema the resolver cannot follow compiles without a static contract, as an out-of-grammar inline schema does, and the runtime derives its MCP JSON Schema from the real zod object. A reference that resolves but whose schema leaves the grammar is `AB4814`. |
12871287
| `AB4839` | error | A CLI route's `inputSchema` reference chain is cyclic — `a` → `b` → `a`, within one module or across several: every visited `<module>#<binding>` is recorded and revisiting one stops the walk. The message is `CLI route <path> inputSchema: <chain> is a reference cycle.` and prints the cycle; it is reported on the route module, with the same recovery and the same CLI-only rule as `AB4838`. |
1288-
| `AB4840` | error | An event route's `preflight` gate (#595) is not the one physically cheap form the compiler can bundle on its own. Rejected: `preflight` declared inline in the route module (`export const preflight = …`, `export function preflight`) or exported more than once; re-exported under a binding other than `default` (`export { gate as preflight } from './gate.js'`, `export { preflight } from './gate.js'`); re-exported from a non-relative specifier (a bare package such as `'@scope/gate'`); a relative target that is missing, unreadable, or part of a re-export cycle; a target default export that cannot be followed through an acyclic chain of relative default re-exports; or a target default export that is not a function the scan can see. The message names the route module and, once a re-export was found, its specifier. Judged statically when the route graph compiles, so `inspect`, `validate`, `build`, and `dev` all report it, once per route with the route module as `sourcePath`; the route compiles without a gate beside the error, and the build fails rather than silently taking the expensive rendered path. Write exactly `export { default as preflight } from './<name>.js'` in the route module, and make that module default-export one sync or async function receiving `{ canonical, host, signal, terminal }` and returning `'execute'`, `{ outcome: 'continue' }`, or `{ outcome: 'deny', reason }`. |
1289-
| `AB4841` | error | An event route's static required-provider declaration (#595) does not select a known set of conventional providers: `config.providers` is not an array of provider-key strings; a key is declared more than once; a key is the reserved `processLifetime`; or a key matches no conventional provider the route graph discovered under `src/providers/`. The message names the route and every offending key. Declare each key exactly once, spelled as the camel-cased stem of its `src/providers/<name>.*` module, drop `processLifetime`, declare `[]` to mount `processLifetime` alone, or omit `config.providers` to preserve the all-provider compatibility default. |
1288+
| `AB4850` | error | An event route's `preflight` gate (#595) is not the one physically cheap form the compiler can bundle on its own. Rejected: `preflight` declared inline in the route module (`export const preflight = …`, `export function preflight`) or exported more than once; re-exported under a binding other than `default` (`export { gate as preflight } from './gate.js'`, `export { preflight } from './gate.js'`); re-exported from a non-relative specifier (a bare package such as `'@scope/gate'`); a relative target that is missing, unreadable, or part of a re-export cycle; a target default export that cannot be followed through an acyclic chain of relative default re-exports; or a target default export that is not a function the scan can see. The message names the route module and, once a re-export was found, its specifier. Judged statically when the route graph compiles, so `inspect`, `validate`, `build`, and `dev` all report it, once per route with the route module as `sourcePath`; the route compiles without a gate beside the error, and the build fails rather than silently taking the expensive rendered path. Write exactly `export { default as preflight } from './<name>.js'` in the route module, and make that module default-export one sync or async function receiving `{ canonical, host, signal, terminal }` and returning `'execute'`, `{ outcome: 'continue' }`, or `{ outcome: 'deny', reason }`. |
1289+
| `AB4851` | error | An event route's static required-provider declaration (#595) does not select a known set of conventional providers: `config.providers` is not an array of provider-key strings; a key is declared more than once; a key is the reserved `processLifetime`; or a key matches no conventional provider the route graph discovered under `src/providers/`. The message names the route and every offending key. Declare each key exactly once, spelled as the camel-cased stem of its `src/providers/<name>.*` module, drop `processLifetime`, declare `[]` to mount `processLifetime` alone, or omit `config.providers` to preserve the all-provider compatibility default. |
12901290
| `AB4940` | error | A conventional provider module has no default export or its default export is not a function. Default-export a factory receiving `{ invocation, plugin, signal }`. |
12911291
| `AB4941` | error | Two provider filenames derive the same camel-cased provider key. Rename one file so every provider key is unique. |
12921292
| `AB4942` | error | A provider filename derives the reserved `processLifetime` key. Rename the file so its camel-cased key does not collide with the framework-owned provider. |

‎packages/agent-bundle/src/routes/contract.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const unwrappedExpression = (expression: ts.Expression): ts.Expression => {
2525
};
2626

2727
const diagnostic = (
28-
code: 'AB4810' | 'AB4811' | 'AB4830' | 'AB4840' | 'AB4940',
28+
code: 'AB4810' | 'AB4811' | 'AB4830' | 'AB4850' | 'AB4940',
2929
message: string,
3030
sourcePath: string,
3131
recovery: string,
@@ -360,7 +360,7 @@ export const discoverEventRoutePreflight = (
360360
if (preflightExports !== 1 || declarations.length !== 1) {
361361
return Object.freeze({
362362
diagnostics: Object.freeze([diagnostic(
363-
'AB4840',
363+
'AB4850',
364364
`Event route module ${relativePath} exports preflight, but it is not exactly one named default re-export from a separate module.`,
365365
sourcePath,
366366
eventPreflightRecovery,
@@ -372,7 +372,7 @@ export const discoverEventRoutePreflight = (
372372
if (!isRelativeSpecifier(declaration!.specifier)) {
373373
return Object.freeze({
374374
diagnostics: Object.freeze([diagnostic(
375-
'AB4840',
375+
'AB4850',
376376
`Event route module ${relativePath} re-exports preflight from non-relative specifier ${JSON.stringify(declaration!.specifier)}.`,
377377
sourcePath,
378378
eventPreflightRecovery,
@@ -384,7 +384,7 @@ export const discoverEventRoutePreflight = (
384384
if (followed === undefined) {
385385
return Object.freeze({
386386
diagnostics: Object.freeze([diagnostic(
387-
'AB4840',
387+
'AB4850',
388388
`Event route module ${relativePath} re-exports preflight from ${JSON.stringify(declaration!.specifier)}, but that target is missing, unreadable, or cyclic.`,
389389
sourcePath,
390390
eventPreflightRecovery,
@@ -397,7 +397,7 @@ export const discoverEventRoutePreflight = (
397397
return Object.freeze({
398398
candidateSource: followed.source,
399399
diagnostics: Object.freeze([diagnostic(
400-
'AB4840',
400+
'AB4850',
401401
`Event route module ${relativePath} re-exports preflight from ${JSON.stringify(declaration!.specifier)}, but its default export cannot be followed through an acyclic relative module chain.`,
402402
sourcePath,
403403
eventPreflightRecovery,
@@ -408,7 +408,7 @@ export const discoverEventRoutePreflight = (
408408
return Object.freeze({
409409
candidateSource: followed.source,
410410
diagnostics: Object.freeze([diagnostic(
411-
'AB4840',
411+
'AB4850',
412412
`Event route module ${relativePath} re-exports preflight from ${JSON.stringify(declaration!.specifier)}, whose default export is not a function.`,
413413
sourcePath,
414414
eventPreflightRecovery,

0 commit comments

Comments
 (0)