diff --git a/docs/site/capabilities/background-jobs.md b/docs/site/capabilities/background-jobs.md
index c1b2a5cf6..0281c4bce 100644
--- a/docs/site/capabilities/background-jobs.md
+++ b/docs/site/capabilities/background-jobs.md
@@ -303,7 +303,7 @@ The runtime scans workers/jobs (the primary user jobs directory) fo
default-exported handlers, and also includes plugin-contributed handlers from
plugins/workers/jobs (and plugins/triggers/jobs when present). The
generated registry lands at
-.netscript/generated/plugin-workers/jobs.registry.ts, keyed by each handler's
+.netscript/generated/plugin-workers/job-registry.ts, keyed by each handler's
id. Background execution runs from plugins/workers/bin/combined.ts,
a separate process from the :8091 API service — the API enqueues, the
runner executes. Set WORKERS_CONCURRENCY on the worker background process when you
diff --git a/docs/site/glossary.md b/docs/site/glossary.md
index b8961d501..a4a545b95 100644
--- a/docs/site/glossary.md
+++ b/docs/site/glossary.md
@@ -64,7 +64,7 @@ These are the words you will meet first — in the [Quickstart](/quickstart/), t
] }) }}
{{ comp.apiTable({ caption: "R–S", rows: [
- { name: "registry", type: "generated registry file", desc: "A generated index the runtime reads to discover what your app contains without runtime reflection. netscript plugin list emits the plugin registry, and the workers profile generates a jobs registry (for example to .netscript/generated/plugin-workers/jobs.registry.ts, keyed by job id). Registries are derived artifacts — regenerate them, do not hand-edit. See Explanation → Plugin system." },
+ { name: "registry", type: "generated registry file", desc: "A generated index the runtime reads to discover what your app contains without runtime reflection. netscript plugin list emits the plugin registry, and the workers profile generates a jobs registry (for example to .netscript/generated/plugin-workers/job-registry.ts, keyed by job id). Registries are derived artifacts — regenerate them, do not hand-edit. See Explanation → Plugin system." },
{ name: "saga", type: "defineSaga(id)…build()", desc: "A durable, message-driven workflow authored with the fluent builder from @netscript/plugin-sagas-core: defineSaga(id).durability('t1').state<S>({...}).on<Type,Payload>(type, handler).build(). Each .on(...) handler reacts to a message and returns effects such as sagaComplete({...}). Runtime state persists to a chosen saga store backend (kv | prisma). The sagas service lists registered sagas at /api/v1/sagas/sagas on port 8092. See Tutorial → Durable workflow and Capabilities → Durable sagas." },
{ name: "saga store backend", type: "NETSCRIPT_SAGA_STORE=kv|prisma", desc: "Where a durable saga runtime persists its checkpointed state — selectable as 'kv' or 'prisma' via NETSCRIPT_SAGA_STORE (or appsettings sagas.store.backend) and constructed with createDurableSagaRuntime({ backend, prisma }). The selection is mandatory — the runtime throws if it is unset, and Prisma mode requires a client. This is distinct from a saga's durability tier. See Capabilities → Durable sagas and Explanation → Durability model." },
{ name: "service", type: "defineService(router, {...})", desc: "An oRPC HTTP application. Local app services use a one-shot defineService(router, { name, version, port, openapi }) call (the users service on port 3001), while plugin API services use the fluent createService(router, {...}).withCors()…serve() builder — two construction APIs in the same project. oRPC is served at /api/rpc/*; the service layer also offers an authn/authz middleware seam (.withAuthn() / .withAuthz()). See Capabilities → Services and reference/service/." },
diff --git a/docs/site/how-to/author-a-plugin.md b/docs/site/how-to/author-a-plugin.md
index ba816554e..17a096c3c 100644
--- a/docs/site/how-to/author-a-plugin.md
+++ b/docs/site/how-to/author-a-plugin.md
@@ -269,7 +269,7 @@ canonical location:
Listing the plugin is not enough — the runtime addresses contributions through a **generated
registry**. For a worker-archetype plugin, the generator scans `plugins//jobs/` and writes a
-jobs registry (e.g. `.netscript/generated/plugin-/jobs.registry.ts`) keyed by each handler's
+jobs registry (e.g. `.netscript/generated/plugin-/job-registry.ts`) keyed by each handler's
`id`. Generate it:
```sh
diff --git a/packages/cli/src/kernel/assets/embedded.generated.ts b/packages/cli/src/kernel/assets/embedded.generated.ts
index ffeaf01b4..586dfa7df 100644
--- a/packages/cli/src/kernel/assets/embedded.generated.ts
+++ b/packages/cli/src/kernel/assets/embedded.generated.ts
@@ -134,7 +134,7 @@ const template_063 =
const template_064 =
"{{__slot0__}}\n\nimport { resolve } from 'node:path';\nimport type { DistributedApplicationBuilder } from '{{__slot1__}}';\nimport type { NetScriptConfig } from '{{__slot2__}}';\nimport type { InfrastructureContext } from './register-infrastructure.mjs';\n\ninterface DbCliTarget {\n readonly configKey: string;\n readonly resourceKey: string;\n readonly databaseName: string;\n readonly envKey: string;\n readonly engine: string;\n readonly taskSuffix: string;\n readonly workdir: string;\n readonly resource: InfrastructureContext['primaryDatabase'];\n}\n\n/**\n * Short-circuit normal AppHost startup when invoked for a database CLI operation.\n *\n * @param builder - Aspire distributed application builder.\n * @param config - Parsed NetScript config.\n * @param infrastructure - Registered infrastructure resources.\n * @param appHostDir - Absolute project root.\n * @returns true when DB CLI mode handled the invocation.\n */\nexport async function tryHandleDbCliMode(\n builder: DistributedApplicationBuilder,\n config: NetScriptConfig,\n infrastructure: InfrastructureContext,\n appHostDir: string,\n): Promise {\n const configuration = await builder.getConfiguration();\n const operation = await readValue(\n configuration,\n 'prisma-operation',\n 'NETSCRIPT_PRISMA_OPERATION',\n );\n if (!operation) {\n return false;\n }\n\n const targets = resolveTargets(\n await readValue(\n configuration,\n 'prisma-target',\n 'NETSCRIPT_PRISMA_TARGET',\n ) ?? config.PrimaryDatabase ?? undefined,\n appHostDir,\n infrastructure,\n );\n\n const migrationName = await readValue(\n configuration,\n 'prisma-name',\n 'NETSCRIPT_PRISMA_NAME',\n );\n\n for (const target of targets) {\n const taskName = `db:${operation}:${target.taskSuffix}`;\n let resource = await builder.addExecutable(\n `prisma-${operation}-${target.configKey}`,\n 'deno',\n target.workdir,\n ['task', taskName],\n );\n\n if (target.engine === 'Sqlite') {\n const sqliteUrl = `file:./${target.databaseName}`;\n resource = await resource\n .withEnvironment('DATABASE_URL', sqliteUrl)\n .withEnvironment(target.envKey, sqliteUrl);\n }\n\n if (target.resource) {\n resource = await resource\n .withEnvironment('DATABASE_URL', target.resource)\n .withEnvironment(target.envKey, target.resource)\n .withReference(target.resource)\n .waitFor(target.resource);\n }\n\n if (migrationName) {\n resource = await resource.withEnvironment('PRISMA_MIGRATION_NAME', migrationName);\n }\n }\n\n return true;\n}\n\nfunction resolveTargets(\n requested: string | undefined,\n appHostDir: string,\n infrastructure: InfrastructureContext,\n): DbCliTarget[] {\n const targets: Record = {\n{{__slot3__}}\n };\n const values = Object.values(targets);\n if (requested === 'all') {\n return values;\n }\n if (!requested && values.length === 1) {\n return values;\n }\n const match = values.find((target) =>\n target.configKey === requested || target.databaseName === requested\n );\n if (!match) {\n throw new Error(`Unknown database target: ${requested ?? '(default)'}`);\n }\n return [match];\n}\n\nasync function readValue(\n configuration: {\n readonly getConfigValue: (key: string) => string | Promise | undefined;\n },\n key: string,\n envName: string,\n): Promise {\n const configured = await configuration.getConfigValue(key);\n return configured ?? process.env[envName];\n}\n";
const template_065 =
- "{{__slot0__}}\n\nimport { resolve } from 'node:path';\nimport { parseAppSettings } from '{{__slot1__}}';\nimport type { DistributedApplicationBuilder } from '{{__slot2__}}';\nimport { configureDashboard } from '{{__slot3__}}';\nimport { registerInfrastructure } from '{{__slot4__}}';\nimport { tryHandleDbCliMode } from '{{__slot5__}}';\nimport { registerServices } from '{{__slot6__}}';\nimport { registerPlugins } from '{{__slot7__}}';\nimport { registerBackgroundProcessors } from '{{__slot8__}}';\nimport { registerApps } from '{{__slot9__}}';\nimport { registerTools } from '{{__slot10__}}';\n\n/**\n * Creates and configures a NetScript AppHost by parsing the project\n * configuration and registering all resources with the Aspire SDK builder.\n *\n * Registration order follows C# NuGet semantics:\n * 1. Dashboard OTLP endpoint configuration\n * 2. Infrastructure (databases + caches)\n * 3. DB CLI short-circuit mode\n * 4. Services (two-pass with cross-reference wiring)\n * 5. Plugins (two-pass with plugin→plugin + plugin→service refs)\n * 6. Background processors (workers, sagas, triggers)\n * 7. Applications (web apps, Tauri desktop, task apps)\n * 8. Development tools (Prisma Studio, etc.)\n *\n * @param builder - The Aspire distributed application builder\n * @param configPath - Path to appsettings.json\n */\nexport async function createNetScriptAppHost(\n builder: DistributedApplicationBuilder,\n configPath: string,\n): Promise {\n const { config } = await parseAppSettings(configPath);\n // apphost.mts lives under `aspire/` to keep the Node.js package graph\n // isolated from the Deno workspace root. Workspace paths declared in\n // appsettings.json (services, apps, plugins, workers) are relative to the\n // project root — which is one level up from `builder.appHostDirectory`.\n const appHostDir = resolve(await builder.appHostDirectory(), '..');\n\n // 1. Configure dashboard OTLP endpoint\n configureDashboard(config);\n\n // 2. Register infrastructure (databases + caches)\n const infrastructure = await registerInfrastructure(builder, config, appHostDir);\n\n // 3. DB CLI mode — short-circuit if active\n if (await tryHandleDbCliMode(builder, config, infrastructure, appHostDir)) {\n return;\n }\n\n // 4. Register services (two-pass: create all, then wire cross-refs)\n const services = await registerServices(builder, config, infrastructure, appHostDir);\n\n // 5. Register plugins (two-pass: create all, then wire plugin→plugin refs)\n const plugins = await registerPlugins(builder, config, infrastructure, services, appHostDir);\n\n // 6. Register background processors\n await registerBackgroundProcessors(builder, config, infrastructure, services, plugins, appHostDir);\n\n // 7. Register applications\n await registerApps(builder, config, infrastructure, services, plugins, appHostDir);\n\n // 8. Register development tools\n await registerTools(builder, config, infrastructure, appHostDir);\n}\n";
+ "{{__slot0__}}\n\nimport { resolve } from 'node:path';\nimport { parseAppSettings } from '{{__slot1__}}';\nimport type { DistributedApplicationBuilder } from '{{__slot2__}}';\nimport { configureDashboard } from '{{__slot3__}}';\nimport { registerInfrastructure } from '{{__slot4__}}';\nimport { tryHandleDbCliMode } from '{{__slot5__}}';\nimport { registerServices, wireServiceReferences } from '{{__slot6__}}';\nimport { registerPlugins } from '{{__slot7__}}';\nimport { registerBackgroundProcessors } from '{{__slot8__}}';\nimport { registerApps } from '{{__slot9__}}';\nimport { registerTools } from '{{__slot10__}}';\n\n/**\n * Creates and configures a NetScript AppHost by parsing the project\n * configuration and registering all resources with the Aspire SDK builder.\n *\n * Registration order follows C# NuGet semantics:\n * 1. Dashboard OTLP endpoint configuration\n * 2. Infrastructure (databases + caches)\n * 3. DB CLI short-circuit mode\n * 4. Services (creation pass)\n * 5. Plugins (two-pass with plugin→plugin + plugin→service refs)\n * 6. Service reference wiring (service→service + service→plugin refs)\n * 7. Background processors (workers, sagas, triggers)\n * 8. Applications (web apps, Tauri desktop, task apps)\n * 9. Development tools (Prisma Studio, etc.)\n *\n * @param builder - The Aspire distributed application builder\n * @param configPath - Path to appsettings.json\n */\nexport async function createNetScriptAppHost(\n builder: DistributedApplicationBuilder,\n configPath: string,\n): Promise {\n const { config } = await parseAppSettings(configPath);\n // apphost.mts lives under `aspire/` to keep the Node.js package graph\n // isolated from the Deno workspace root. Workspace paths declared in\n // appsettings.json (services, apps, plugins, workers) are relative to the\n // project root — which is one level up from `builder.appHostDirectory`.\n const appHostDir = resolve(await builder.appHostDirectory(), '..');\n\n // 1. Configure dashboard OTLP endpoint\n configureDashboard(config);\n\n // 2. Register infrastructure (databases + caches)\n const infrastructure = await registerInfrastructure(builder, config, appHostDir);\n\n // 3. DB CLI mode — short-circuit if active\n if (await tryHandleDbCliMode(builder, config, infrastructure, appHostDir)) {\n return;\n }\n\n // 4. Register services\n const services = await registerServices(builder, config, infrastructure, appHostDir);\n\n // 5. Register plugins (two-pass: create all, then wire plugin→plugin refs)\n const plugins = await registerPlugins(builder, config, infrastructure, services, appHostDir);\n\n // 6. Wire service references after plugin APIs exist\n await wireServiceReferences(config, services, plugins);\n\n // 7. Register background processors\n await registerBackgroundProcessors(builder, config, infrastructure, services, plugins, appHostDir);\n\n // 8. Register applications\n await registerApps(builder, config, infrastructure, services, plugins, appHostDir);\n\n // 9. Register development tools\n await registerTools(builder, config, infrastructure, appHostDir);\n}\n";
const template_066 =
"{{__slot0__}}\n\nimport type { DistributedApplicationBuilder } from '{{__slot1__}}';\nimport type { NetScriptConfig } from '{{__slot2__}}';\nimport type { InfrastructureContext } from './register-infrastructure.mjs';\nimport {\n buildOtelEnvVars,\n buildViteEnvVarName,\n extractDependencies,\n extractPluginReferences,\n extractServiceReferences,\n resolveWorkspacePath,\n} from '{{__slot3__}}';\n\ninterface EndpointProvider {\n getEndpoint(protocol: string): Promise | string | undefined;\n}\n\nasync function getResourceEndpoint(resource: unknown, protocol: string): Promise {\n if (!resource || typeof resource !== 'object' || !('getEndpoint' in resource)) {\n return undefined;\n }\n return await (resource as EndpointProvider).getEndpoint(protocol);\n}\n\n/**\n * Registers application resources (web apps, Tauri desktop apps, and tasks)\n * with the Aspire SDK builder.\n *\n * - `app` type: registered via `addExecutable()` with VITE env var injection\n * for service discovery (full, shorthand, and server-side formats).\n * - `tauri` type: registered via `addExecutable()` with optional remote\n * app reference.\n * - `task` type: registered via `addExecutable()` with simple config.\n *\n * All types receive OTEL telemetry (full executable env set), optional\n * HTTP endpoint binding, and optional KV cache dependency injection.\n *\n * @param builder - The Aspire distributed application builder\n * @param config - Parsed NetScript configuration\n * @param infrastructure - Infrastructure context with database/cache resources\n * @param services - Map of registered service resources (for endpoint-based env var wiring and VITE injection)\n * @param plugins - Map of registered plugin resources (for endpoint-based env var wiring and VITE injection)\n * @param appHostDir - Absolute path to the AppHost directory\n * @returns Map of registered app resources keyed by name\n */\nexport async function registerApps(\n builder: DistributedApplicationBuilder,\n config: NetScriptConfig,\n infrastructure: InfrastructureContext,\n services: Map,\n plugins: Map,\n appHostDir: string,\n): Promise