akanjs 3.0.0-alpha.21 → 3.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/base/baseEnv.ts +1 -1
  2. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  3. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  4. package/package.json +1 -1
  5. package/server/akanOption.ts +6 -10
  6. package/server/akanServer.ts +16 -14
  7. package/server/di/diLifecycle.ts +4 -4
  8. package/server/mcp/McpDispatcher.ts +2 -2
  9. package/server/mcp/McpRouter.ts +6 -4
  10. package/server/resolver/signal.resolver.ts +2 -2
  11. package/service/injectInfo.ts +8 -3
  12. package/service/predefinedAdaptor/database.adaptor.ts +24 -22
  13. package/service/predefinedAdaptor/queue.adaptor.ts +5 -2
  14. package/service/predefinedAdaptor/solidQueue.adaptor.ts +6 -2
  15. package/service/predefinedAdaptor/solidSqlite.ts +11 -9
  16. package/signal/agent.signal.ts +1 -5
  17. package/signal/guards.ts +3 -11
  18. package/signal/middleware.ts +3 -3
  19. package/signal/signalContext.ts +3 -3
  20. package/test/index.ts +1 -1
  21. package/test/signalTestRuntime.ts +5 -5
  22. package/test/testServer.ts +8 -5
  23. package/types/base/baseEnv.d.ts +1 -1
  24. package/types/server/akanOption.d.ts +7 -7
  25. package/types/server/akanServer.d.ts +4 -7
  26. package/types/server/di/diLifecycle.d.ts +2 -2
  27. package/types/server/mcp/McpDispatcher.d.ts +2 -2
  28. package/types/server/mcp/McpRouter.d.ts +3 -2
  29. package/types/server/resolver/signal.resolver.d.ts +2 -2
  30. package/types/service/injectInfo.d.ts +2 -2
  31. package/types/service/predefinedAdaptor/database.adaptor.d.ts +2 -2
  32. package/types/service/predefinedAdaptor/queue.adaptor.d.ts +3 -2
  33. package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +6 -2
  34. package/types/service/predefinedAdaptor/solidSqlite.d.ts +2 -2
  35. package/types/signal/agent.signal.d.ts +1 -6
  36. package/types/signal/guards.d.ts +2 -4
  37. package/types/signal/middleware.d.ts +7 -7
  38. package/types/signal/signalContext.d.ts +2 -2
  39. package/types/test/index.d.ts +1 -1
  40. package/types/test/signalTestRuntime.d.ts +2 -3
  41. package/types/test/testServer.d.ts +6 -4
package/base/baseEnv.ts CHANGED
@@ -15,7 +15,7 @@ export interface BaseEnv {
15
15
  operationMode: "local" | "edge" | "cloud" | "module";
16
16
  databaseMode?: DatabaseMode;
17
17
  }
18
- export type BackendEnv = BaseEnv & {
18
+ export type BackendEnv = {
19
19
  hostname?: string | null;
20
20
  port?: number;
21
21
  database?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.21",
3
+ "version": "3.0.0-alpha.23",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -14,7 +14,7 @@ export interface AdaptorOverride {
14
14
  * App/library server option builder: use objects, signal middleware, adaptor overrides, web proxies, and the
15
15
  * server settings an app owns — MCP, the agent relay's access policy, and the LLM the relay speaks to.
16
16
  */
17
- export class AkanOption<Env = unknown> {
17
+ export class AkanOption<Env extends BackendEnv = BackendEnv> {
18
18
  readonly #getUses: ((env: Env) => Record<string, PromiseOrObject<unknown>>)[];
19
19
  readonly #middlewares: MiddlewareCls[] = [];
20
20
  readonly #adaptorOverrides: AdaptorOverride[] = [];
@@ -25,11 +25,7 @@ export class AkanOption<Env = unknown> {
25
25
  constructor() {
26
26
  this.#getUses = [];
27
27
  }
28
- use(
29
- fnOrObject:
30
- | ((env: Env & BackendEnv) => Record<string, PromiseOrObject<unknown>>)
31
- | Record<string, PromiseOrObject<unknown>>,
32
- ) {
28
+ use(fnOrObject: ((env: Env) => Record<string, PromiseOrObject<unknown>>) | Record<string, PromiseOrObject<unknown>>) {
33
29
  if (typeof fnOrObject === "function")
34
30
  this.#getUses.push(fnOrObject as (env: Env) => Record<string, PromiseOrObject<unknown>>);
35
31
  else this.#getUses.push(() => fnOrObject);
@@ -58,8 +54,8 @@ export class AkanOption<Env = unknown> {
58
54
  return this;
59
55
  }
60
56
  /**
61
- * Who may spend the LLM key through the `runAgentTurn` relay the `AgentRelayAccess` policy, which defaults to
62
- * allowing everyone because the framework has no account model to gate on. `null` reopens it.
57
+ * Who may spend the LLM key through the `runAgentTurn` relay. With no policy the call is refused the same
58
+ * answer `None` gives — because the framework has no account model to gate on. `null` clears the policy.
63
59
  */
64
60
  setAgentAccess(policy: AgentRelayPolicy | null) {
65
61
  this.#agentAccess = policy;
@@ -71,7 +67,7 @@ export class AkanOption<Env = unknown> {
71
67
  else this.#getLlms.push(() => llmOrFn);
72
68
  return this;
73
69
  }
74
- getUses(env: Env & BackendEnv): Record<string, PromiseOrObject<unknown>> {
70
+ getUses(env: Env): Record<string, PromiseOrObject<unknown>> {
75
71
  const uses = this.#getUses.map((fn) => fn(env));
76
72
  return Object.assign({}, ...uses);
77
73
  }
@@ -90,7 +86,7 @@ export class AkanOption<Env = unknown> {
90
86
  getAgentAccess(): AgentRelayPolicy | null | undefined {
91
87
  return this.#agentAccess;
92
88
  }
93
- getLlm(env: Env & BackendEnv): LlmOption {
89
+ getLlm(env: Env): LlmOption {
94
90
  return Object.assign({}, ...this.#getLlms.map((fn) => fn(env)));
95
91
  }
96
92
  }
@@ -1,4 +1,4 @@
1
- import { type BaseEnv, getEnv } from "akanjs/base";
1
+ import { type BackendEnv, type BaseEnv, getEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import { DictionaryLookup } from "akanjs/dictionary";
4
4
  import type {
@@ -31,7 +31,7 @@ import type { HttpRoutes, SignalRoutes, WebsocketRoutes } from "./types";
31
31
  import type { WebRouter } from "./webRouter";
32
32
 
33
33
  export interface AkanServerProps extends AkanLibProps {
34
- env?: BaseEnv;
34
+ env?: BackendEnv;
35
35
  prefix?: string;
36
36
  websocketPrefix?: string;
37
37
  openapi?: boolean;
@@ -113,7 +113,7 @@ export class AkanServer {
113
113
  readonly logger: Logger;
114
114
  readonly name: string;
115
115
  readonly libs: AkanLib[];
116
- readonly env: BaseEnv & { database?: DatabaseConfig; solid?: SolidConfig };
116
+ readonly env: BackendEnv;
117
117
  prefix = "/api";
118
118
  websocketPrefix = "/ws";
119
119
  openapi = AkanServer.#isOpenApiEnvEnabled();
@@ -129,7 +129,7 @@ export class AkanServer {
129
129
  #metricsTimer: Timer | null = null;
130
130
  constructor(
131
131
  name = "AkanServer",
132
- env = {},
132
+ env: BackendEnv = {},
133
133
  serverMode: "federation" | "batch" | "all" = (process.env.SERVER_MODE as
134
134
  | "federation"
135
135
  | "batch"
@@ -141,7 +141,7 @@ export class AkanServer {
141
141
  this.name = name;
142
142
  this.logger = new Logger(name);
143
143
  this.libs = libs;
144
- this.env = Object.assign(getEnv(), env);
144
+ this.env = { ...env };
145
145
  this.openapi = options?.openapi ?? this.openapi;
146
146
 
147
147
  libs.forEach((lib) => {
@@ -217,17 +217,18 @@ export class AkanServer {
217
217
 
218
218
  inspectConsole(): AkanServerConsoleInfo {
219
219
  this.#assertCanGet();
220
+ const env = getEnv();
220
221
  return {
221
222
  name: this.name,
222
223
  status: this.status,
223
224
  serverMode: this.serverMode,
224
225
  env: {
225
- appName: this.env.appName,
226
- environment: this.env.environment,
227
- operationMode: this.env.operationMode,
228
- repoName: this.env.repoName,
229
- serveDomain: this.env.serveDomain,
230
- databaseMode: this.env.databaseMode,
226
+ appName: env.appName,
227
+ environment: env.environment,
228
+ operationMode: env.operationMode,
229
+ repoName: env.repoName,
230
+ serveDomain: env.serveDomain,
231
+ databaseMode: env.databaseMode,
231
232
  },
232
233
  services: [...this.#di.registry.serviceCls.keys()].sort((a, b) => a.localeCompare(b)),
233
234
  signals: [...this.#di.registry.serverSignalCls.keys()].sort((a, b) => a.localeCompare(b)),
@@ -514,13 +515,14 @@ export class AkanServer {
514
515
  }
515
516
 
516
517
  #createBuiltinRoutes(): HttpRoutes {
518
+ const { appName } = getEnv();
517
519
  const openapiRoutes: HttpRoutes = this.openapi
518
520
  ? {
519
521
  "/openapi.json": {
520
522
  GET: () =>
521
523
  Response.json(
522
524
  createOpenApiDocument(FetchSerializer.serializeRegistry(this.#di.live).signal, {
523
- title: `${this.env.appName} API`,
525
+ title: `${appName} API`,
524
526
  version: "0.0.0",
525
527
  servers: this.#getOpenApiServers(),
526
528
  resolveDescription: AkanServer.#createDescriptionResolver(),
@@ -537,7 +539,7 @@ export class AkanServer {
537
539
  env: this.env,
538
540
  live: this.#di.live,
539
541
  middleware: new Map(this.#di.modules.middleware),
540
- instructions: `Domain tools for the ${this.env.appName} app.`,
542
+ instructions: `Domain tools for the ${appName} app.`,
541
543
  ...this.mcpOption,
542
544
  readOnly: this.mcpReadOnly,
543
545
  auth: this.mcpAuth,
@@ -549,7 +551,7 @@ export class AkanServer {
549
551
 
550
552
  const devtoolsRoutes = new DevtoolsRouter({
551
553
  di: this.#di,
552
- env: this.env,
554
+ env: getEnv(),
553
555
  name: this.name,
554
556
  serverMode: this.serverMode,
555
557
  prefix: this.prefix,
@@ -1,4 +1,4 @@
1
- import type { BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv, getEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import {
4
4
  type Adaptor,
@@ -53,7 +53,7 @@ export class DiLifecycle {
53
53
  adaptorStages: [] as string[][],
54
54
  serviceStages: [] as string[][],
55
55
  };
56
- readonly #env: BaseEnv;
56
+ readonly #env: BackendEnv;
57
57
  readonly #libs: AkanLib[];
58
58
  readonly #database = new Map<string, DatabaseModule>();
59
59
  readonly #service = new Map<string, ServiceModule>();
@@ -88,10 +88,10 @@ export class DiLifecycle {
88
88
  return !names.some((name) => process.env[name] === "false" || process.env[name] === "0");
89
89
  }
90
90
 
91
- constructor(env: BaseEnv, serverMode: "federation" | "batch" | "all", ...libs: AkanLib[]) {
91
+ constructor(env: BackendEnv, serverMode: "federation" | "batch" | "all", ...libs: AkanLib[]) {
92
92
  this.#env = env;
93
93
 
94
- this.#predefinedAdaptor = { ...getPredefinedAdaptor(env.databaseMode ?? "single") };
94
+ this.#predefinedAdaptor = { ...getPredefinedAdaptor(getEnv().databaseMode ?? "single") };
95
95
  this.#libs = libs;
96
96
  this.#service.set("base", {
97
97
  service: srv.base,
@@ -1,4 +1,4 @@
1
- import { type BaseEnv, ENDPOINT_META } from "akanjs/base";
1
+ import { type BackendEnv, ENDPOINT_META } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import { DictionaryLookup } from "akanjs/dictionary";
4
4
  import { NoDocumentError } from "akanjs/document";
@@ -37,7 +37,7 @@ export class McpPromptError extends Error {
37
37
 
38
38
  interface McpDispatcherProps {
39
39
  registry: InjectRegistry;
40
- env: BaseEnv;
40
+ env: BackendEnv;
41
41
  live: LiveRegistry;
42
42
  middleware: Map<string, MiddlewareCls>;
43
43
  /** The one language error text is resolved in, matching the catalogue the client was handed. */
@@ -1,4 +1,4 @@
1
- import type { BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv, getEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import { DictionaryLookup } from "akanjs/dictionary";
4
4
  import type { InjectRegistry, LiveRegistry } from "akanjs/service";
@@ -25,7 +25,8 @@ import { McpEventStream } from "./McpEventStream";
25
25
 
26
26
  export interface McpRouterProps {
27
27
  registry: InjectRegistry;
28
- env: BaseEnv;
28
+ /** Spread into every `SignalContext` this router's dispatcher builds; middleware reads it. */
29
+ env: BackendEnv;
29
30
  live: LiveRegistry;
30
31
  middleware: Map<string, MiddlewareCls>;
31
32
  path?: string;
@@ -143,7 +144,7 @@ export class McpRouter {
143
144
  McpRouter.logger.warn(
144
145
  "MCP is enabled but published nothing. Every candidate was refused; see the reasons below.",
145
146
  );
146
- for (const { key, reason } of refusals) McpRouter.logger.warn(`MCP did not expose "${key}": ${reason}`);
147
+ for (const { key, reason } of refusals) McpRouter.logger.verbose(`MCP did not expose "${key}": ${reason}`);
147
148
  for (const { key, reason } of undescribed)
148
149
  McpRouter.logger.warn(`MCP exposed "${key}" with no description: ${reason}`);
149
150
  } catch (error) {
@@ -461,7 +462,8 @@ export class McpRouter {
461
462
  }
462
463
 
463
464
  #serverInfo() {
464
- return { name: `${this.#props.env.appName}-mcp`, version: this.#props.version ?? "0.0.0" };
465
+ const env = getEnv();
466
+ return { name: `${env.appName}-mcp`, version: this.#props.version ?? "0.0.0" };
465
467
  }
466
468
 
467
469
  #result(call: McpCall, result: object, cache?: McpCacheHint) {
@@ -1,5 +1,5 @@
1
1
  import {
2
- type BaseEnv,
2
+ type BackendEnv,
3
3
  type Cls,
4
4
  ENDPOINT_META,
5
5
  FIELD_META,
@@ -316,7 +316,7 @@ export class SignalResolver {
316
316
  env,
317
317
  live,
318
318
  middleware,
319
- }: { registry: InjectRegistry; env: BaseEnv; live: LiveRegistry; middleware: Map<string, MiddlewareCls> },
319
+ }: { registry: InjectRegistry; env: BackendEnv; live: LiveRegistry; middleware: Map<string, MiddlewareCls> },
320
320
  ): SignalRoutes {
321
321
  const endpointMeta = endpointCls[ENDPOINT_META] as { [key: string]: EndpointInfo };
322
322
  const routes: HttpRoutes = {};
@@ -1,4 +1,4 @@
1
- import { type BaseEnv, type Cls, INJECT_META } from "akanjs/base";
1
+ import { type BackendEnv, type Cls, INJECT_META } from "akanjs/base";
2
2
  import {
3
3
  type ConstantFieldTypeInput,
4
4
  ConstantRegistry,
@@ -119,7 +119,7 @@ export class InjectInfo<
119
119
  instance: Adaptor | Service,
120
120
  applyCls: AdaptorCls | ServiceCls,
121
121
  registry: InjectRegistry,
122
- env: BaseEnv,
122
+ env: BackendEnv,
123
123
  ) {
124
124
  const injectMap = applyCls[INJECT_META] as Record<string, InjectInfo>;
125
125
  await Promise.all(
@@ -240,7 +240,12 @@ export class InjectInfo<
240
240
  const value = await injectInfo.generateFactory(depInstance);
241
241
  Object.defineProperty(instance, propKey, { value, writable: false, enumerable: true });
242
242
  }
243
- static async #injectEnv(instance: Adaptor | Service, propKey: string, injectInfo: InjectInfo<"env">, env: BaseEnv) {
243
+ static async #injectEnv(
244
+ instance: Adaptor | Service,
245
+ propKey: string,
246
+ injectInfo: InjectInfo<"env">,
247
+ env: BackendEnv,
248
+ ) {
244
249
  const value = await injectInfo.generateFactory(env);
245
250
  Object.defineProperty(instance, propKey, { value, writable: false, enumerable: true });
246
251
  }
@@ -3,7 +3,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { mkdir } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import type { InArgs, InValue, Client as LibsqlClient } from "@libsql/client";
6
- import { type BaseEnv, DEFAULT_VALUE, dayjs, FIELD_META, type PromiseOrObject } from "akanjs/base";
6
+ import { DEFAULT_VALUE, dayjs, FIELD_META, getEnv, type PromiseOrObject } from "akanjs/base";
7
7
  import { type ConstantModel, getDefault } from "akanjs/constant";
8
8
  import {
9
9
  createDocumentId,
@@ -147,7 +147,7 @@ export interface DatabaseAdaptor {
147
147
  getSearchIndex(): SearchIndex | null;
148
148
  }
149
149
 
150
- interface SqliteEnv extends BaseEnv {
150
+ interface SqliteEnv {
151
151
  workspaceRoot?: string;
152
152
  database?: DatabaseConfig;
153
153
  }
@@ -1759,22 +1759,23 @@ export class SqliteDatabase
1759
1759
  extends adapt("sqliteDatabase", ({ env, plug }) => ({
1760
1760
  scheduler: plug(ScheduleAdaptorRole),
1761
1761
  config: env((env: SqliteEnv) => {
1762
- const appName = env.appName ?? "akan";
1763
- const environment = env.environment ?? "local";
1764
- const defaultFile = resolveDefaultSqliteFile({
1765
- appName,
1766
- fileName: `${appName}-${environment}.db`,
1767
- isProduction: process.env.NODE_ENV === "production",
1768
- operationMode: env.operationMode,
1769
- workspaceRoot: env.workspaceRoot,
1770
- });
1762
+ const defaultFile = () => {
1763
+ const { appName, environment, operationMode } = getEnv();
1764
+ return resolveDefaultSqliteFile({
1765
+ appName,
1766
+ fileName: `${appName}-${environment}.db`,
1767
+ isProduction: process.env.NODE_ENV === "production",
1768
+ operationMode,
1769
+ workspaceRoot: env.workspaceRoot,
1770
+ });
1771
+ };
1771
1772
  return {
1772
1773
  journalMode: "WAL",
1773
1774
  busyTimeoutMs: 5000,
1774
1775
  synchronous: "NORMAL",
1775
1776
  foreignKeys: true,
1776
1777
  ...env.database?.sqlite,
1777
- filePath: env.database?.sqlite?.filePath ?? process.env.SQLITE_DATABASE_PATH ?? defaultFile,
1778
+ filePath: env.database?.sqlite?.filePath ?? process.env.SQLITE_DATABASE_PATH ?? defaultFile(),
1778
1779
  search: {
1779
1780
  enabled: env.database?.search?.enabled ?? parseSearchEnabled(process.env.AKAN_SEARCH_ENABLED),
1780
1781
  tokenizer: env.database?.search?.tokenizer ?? process.env.AKAN_SEARCH_TOKENIZER ?? DEFAULT_TOKENIZER,
@@ -1892,21 +1893,22 @@ export class LibsqlDatabase
1892
1893
  extends adapt("libsqlDatabase", ({ env, plug }) => ({
1893
1894
  scheduler: plug(ScheduleAdaptorRole),
1894
1895
  config: env((env: SqliteEnv) => {
1895
- const appName = env.appName ?? "akan";
1896
- const environment = env.environment ?? "local";
1897
- const defaultFile = resolveDefaultSqliteFile({
1898
- appName,
1899
- fileName: `${appName}-${environment}.db`,
1900
- isProduction: process.env.NODE_ENV === "production",
1901
- operationMode: env.operationMode,
1902
- workspaceRoot: env.workspaceRoot,
1903
- });
1896
+ const defaultFile = () => {
1897
+ const { appName, environment, operationMode } = getEnv();
1898
+ return resolveDefaultSqliteFile({
1899
+ appName,
1900
+ fileName: `${appName}-${environment}.db`,
1901
+ isProduction: process.env.NODE_ENV === "production",
1902
+ operationMode,
1903
+ workspaceRoot: env.workspaceRoot,
1904
+ });
1905
+ };
1904
1906
  return {
1905
1907
  url:
1906
1908
  env.database?.libsql?.url ??
1907
1909
  process.env.LIBSQL_URL ??
1908
1910
  process.env.LIBSQL_URI ??
1909
- `file:${env.database?.sqlite?.filePath ?? process.env.SQLITE_DATABASE_PATH ?? defaultFile}`,
1911
+ `file:${env.database?.sqlite?.filePath ?? process.env.SQLITE_DATABASE_PATH ?? defaultFile()}`,
1910
1912
  authToken: env.database?.libsql?.authToken ?? process.env.LIBSQL_AUTH_TOKEN,
1911
1913
  search: {
1912
1914
  enabled: env.database?.search?.enabled ?? parseSearchEnabled(process.env.AKAN_SEARCH_ENABLED),
@@ -1,4 +1,4 @@
1
- import type { BaseEnv } from "akanjs/base";
1
+ import { getEnv } from "akanjs/base";
2
2
  import { adapt } from "../adapt";
3
3
  import type { AkanJob, AkanJobOptions, AkanWorker } from "../ipcTypes";
4
4
  import { RedisCache } from "./cache.adaptor";
@@ -30,7 +30,10 @@ export interface QueueAdaptor {
30
30
  export class BullQueue
31
31
  extends adapt("bullQueue", ({ plug, env }) => ({
32
32
  redis: plug(RedisCache, (redisCache) => redisCache.getClient()),
33
- prefix: env((env: BaseEnv) => `queue-${env.repoName}-${env.appName}-${env.environment}-${env.operationMode}`),
33
+ prefix: env(() => {
34
+ const { repoName, appName, environment, operationMode } = getEnv();
35
+ return `queue-${repoName}-${appName}-${environment}-${operationMode}`;
36
+ }),
34
37
  }))
35
38
  implements QueueAdaptor
36
39
  {
@@ -1,4 +1,5 @@
1
1
  import type { Database, Statement } from "bun:sqlite";
2
+ import { getEnv } from "akanjs/base";
2
3
  import { adapt } from "../adapt";
3
4
  import { type AkanJob, type AkanJobOptions, type AkanWorker, sendAkanIpc } from "../ipcTypes";
4
5
  import type { QueueAdaptor } from "./queue.adaptor";
@@ -56,8 +57,11 @@ class SolidWorker implements AkanWorker {
56
57
  export class SolidQueue
57
58
  extends adapt("solidQueue", ({ env }) => ({
58
59
  config: env((env: SolidEnv) => getSolidConfig(env)),
59
- queueName: env((env: SolidEnv) => `queue-${env.repoName}-${env.appName}-${env.environment}-${env.operationMode}`),
60
- workerId: env((env: SolidEnv) => `${env.appName}-${process.env.AKAN_REPLICA_IDX ?? "0"}-${process.pid}`),
60
+ queueName: env(() => {
61
+ const { repoName, appName, environment, operationMode } = getEnv();
62
+ return `queue-${repoName}-${appName}-${environment}-${operationMode}`;
63
+ }),
64
+ workerId: env(() => `${getEnv().appName}-${process.env.AKAN_REPLICA_IDX ?? "0"}-${process.pid}`),
61
65
  }))
62
66
  implements QueueAdaptor
63
67
  {
@@ -1,7 +1,7 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import type { BaseEnv, Dayjs } from "akanjs/base";
4
+ import { type Dayjs, getEnv } from "akanjs/base";
5
5
  import { resolveDefaultSqliteFile } from "./sqlitePath";
6
6
 
7
7
  export interface SolidConfig {
@@ -14,7 +14,7 @@ export interface SolidConfig {
14
14
  queueLeaseMs?: number;
15
15
  }
16
16
 
17
- export interface SolidEnv extends BaseEnv {
17
+ export interface SolidEnv {
18
18
  workspaceRoot?: string;
19
19
  solid?: SolidConfig;
20
20
  }
@@ -23,18 +23,20 @@ export type SolidValueType = "string" | "number" | "buffer" | "json";
23
23
 
24
24
  const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
25
25
 
26
- export const getSolidConfig = (env: SolidEnv): Required<SolidConfig> => {
27
- const appName = env.appName ?? "akan";
28
- const environment = env.environment ?? "local";
29
- const defaultFile = resolveDefaultSqliteFile({
26
+ const defaultSolidFile = (workspaceRoot?: string) => {
27
+ const { appName, environment, operationMode } = getEnv();
28
+ return resolveDefaultSqliteFile({
30
29
  appName,
31
30
  fileName: `${appName}-${environment}_solid.db`,
32
31
  isProduction: process.env.NODE_ENV === "production",
33
- operationMode: env.operationMode,
34
- workspaceRoot: env.workspaceRoot,
32
+ operationMode,
33
+ workspaceRoot,
35
34
  });
35
+ };
36
+
37
+ export const getSolidConfig = (env: SolidEnv): Required<SolidConfig> => {
36
38
  return {
37
- filePath: env.solid?.filePath ?? process.env.AKAN_SOLID_DB_PATH ?? defaultFile,
39
+ filePath: env.solid?.filePath ?? process.env.AKAN_SOLID_DB_PATH ?? defaultSolidFile(env.workspaceRoot),
38
40
  journalMode: env.solid?.journalMode ?? "WAL",
39
41
  busyTimeoutMs: env.solid?.busyTimeoutMs ?? 5000,
40
42
  synchronous: env.solid?.synchronous ?? "NORMAL",
@@ -10,11 +10,7 @@ import { Req } from "./internalArg";
10
10
  import { serverSignal } from "./serverSignal";
11
11
  import { SignalRegistry } from "./signalRegistry";
12
12
 
13
- export class AgentInternal extends internal(srv.agent, ({ initialize }) => ({
14
- warnOpenRelay: initialize().exec(() => {
15
- AgentRelayAccess.warnIfOpen();
16
- }),
17
- })) {}
13
+ export class AgentInternal extends internal(srv.agent, () => ({})) {}
18
14
 
19
15
  export class AgentEndpoint extends endpoint(srv.agent, ({ mutation }) => ({
20
16
 
package/signal/guards.ts CHANGED
@@ -24,8 +24,8 @@ export type AgentRelayPolicy = (context: SignalContext) => boolean | Promise<boo
24
24
  * Gate for the `runAgentTurn` relay. Every tool runs in the caller's own browser session, so the LLM key is the
25
25
  * one thing this endpoint spends — ungated, any visitor can bill the app's provider through fetch alone.
26
26
  *
27
- * The framework has no account model to gate on, so with no policy registered it allows everyonewhat `Public`
28
- * said before, but with a seam: a product hardens it at boot, e.g.
27
+ * The framework has no account model to gate on, so with no policy registered it refuses every call the same
28
+ * answer `None` gives. An app opens it at boot, e.g.
29
29
  * `AgentRelayAccess.use((context) => !!context.get("account"))`. The policy is the app's; the framework cannot know it.
30
30
  */
31
31
  export class AgentRelayAccess implements Guard {
@@ -44,17 +44,9 @@ export class AgentRelayAccess implements Guard {
44
44
  return !!AgentRelayAccess.#policy;
45
45
  }
46
46
 
47
- /** Runs at boot, after the app entry has evaluated — a module-scope check would fire before `use()` could. */
48
- static warnIfOpen() {
49
- if (AgentRelayAccess.#policy) return;
50
- AgentRelayAccess.#logger.warn(
51
- 'runAgentTurn is open to every caller — anyone can spend the LLM key. Register a policy at boot: AgentRelayAccess.use((context) => !!context.get("account")), or set AKAN_AGENT=false.',
52
- );
53
- }
54
-
55
47
  async canPass(context: SignalContext): Promise<boolean> {
56
48
  const policy = AgentRelayAccess.#policy;
57
- if (!policy) return true;
49
+ if (!policy) return false;
58
50
  try {
59
51
  return await policy(context);
60
52
  } catch (error) {
@@ -1,11 +1,11 @@
1
- import type { BaseEnv, Cls, PromiseOrObject } from "akanjs/base";
1
+ import type { BackendEnv, Cls, PromiseOrObject } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import { type CacheAdaptor, CacheAdaptorRole } from "akanjs/service";
4
4
  import dayjs from "dayjs";
5
5
  import type { SignalContext } from "./signalContext";
6
6
  import { traceCache } from "./trace";
7
7
 
8
- export interface Middleware<Env extends BaseEnv = BaseEnv> {
8
+ export interface Middleware<Env extends BackendEnv = BackendEnv> {
9
9
  use(env: Env): PromiseOrObject<(context: SignalContext, next: () => Promise<unknown>) => PromiseOrObject<unknown>>;
10
10
  }
11
11
 
@@ -14,7 +14,7 @@ export type MiddlewareCls = Cls<Middleware, { readonly refName: string }>;
14
14
  export const middleware = (refName: string) => {
15
15
  return class Middleware {
16
16
  static refName = refName;
17
- async use(env: BaseEnv) {
17
+ async use(env: BackendEnv) {
18
18
  return async (context: SignalContext, next: () => Promise<unknown>) => {
19
19
  return await next();
20
20
  };
@@ -1,5 +1,5 @@
1
1
  import {
2
- type BaseEnv,
2
+ type BackendEnv,
3
3
  type Cls,
4
4
  FIELD_META,
5
5
  INTERNAL_META,
@@ -52,7 +52,7 @@ const isExceptionLike = (error: unknown): error is ExceptionLike => {
52
52
 
53
53
  export class SignalContext<
54
54
  Ctx extends HttpExecutionContext | WebSocketExecutionContext = HttpExecutionContext | WebSocketExecutionContext,
55
- Env extends BaseEnv = BaseEnv,
55
+ Env extends BackendEnv = BackendEnv,
56
56
  > {
57
57
  key: string;
58
58
  transport: SignalTransportType;
@@ -215,7 +215,7 @@ export class SignalContext<
215
215
  * call for every registered middleware — and `Logging` is registered by default.
216
216
  */
217
217
  static #middlewareHandlers = new WeakMap<MiddlewareCls, WeakMap<object, Promise<MiddlewareHandler>>>();
218
- static #getMiddlewareHandler(MiddlewareCls: MiddlewareCls, env: BaseEnv): Promise<MiddlewareHandler> {
218
+ static #getMiddlewareHandler(MiddlewareCls: MiddlewareCls, env: BackendEnv): Promise<MiddlewareHandler> {
219
219
  const byEnv =
220
220
  SignalContext.#middlewareHandlers.get(MiddlewareCls) ?? new WeakMap<object, Promise<MiddlewareHandler>>();
221
221
  SignalContext.#middlewareHandlers.set(MiddlewareCls, byEnv);
package/test/index.ts CHANGED
@@ -13,4 +13,4 @@ export {
13
13
  setupSignalTestTarget,
14
14
  terminateSignalTestContext,
15
15
  } from "./signalTestRuntime";
16
- export { TestServer, type TestServerOptions } from "./testServer";
16
+ export { type TestEnv, TestServer, type TestServerOptions } from "./testServer";
@@ -1,12 +1,12 @@
1
- import type { BackendEnv } from "akanjs/base";
1
+ import type { BackendEnv, BaseEnv } from "akanjs/base";
2
2
  import type { FetchProxy } from "akanjs/fetch";
3
3
  import type { AkanLib } from "akanjs/server";
4
- import { TestServer, type TestServerOptions } from "./testServer";
4
+ import { type TestEnv, TestServer, type TestServerOptions } from "./testServer";
5
5
 
6
6
  export interface SignalTestTarget {
7
7
  type: "app" | "lib";
8
8
  name: string;
9
- env: BackendEnv;
9
+ env: TestEnv;
10
10
  fetch: FetchProxy;
11
11
  libs: AkanLib[];
12
12
  }
@@ -87,7 +87,7 @@ export const setupSignalTestTarget = async <Fetch = FetchProxy>(
87
87
  pendingContext = (async () => {
88
88
  terminatingContext = undefined;
89
89
  const resolvedOptions = { ...configuredOptions, ...options };
90
- const env: BackendEnv = {
90
+ const env: BaseEnv = {
91
91
  repoName: process.env.AKAN_PUBLIC_REPO_NAME ?? "akanjs",
92
92
  serveDomain: process.env.AKAN_PUBLIC_SERVE_DOMAIN ?? "akanjs.com",
93
93
  appName: name,
@@ -103,7 +103,7 @@ export const setupSignalTestTarget = async <Fetch = FetchProxy>(
103
103
  const target: SignalTestTarget = {
104
104
  type,
105
105
  name,
106
- env: targetModule.env,
106
+ env: { ...env, ...targetModule.env },
107
107
  fetch: targetModule.fetch,
108
108
  libs: [...dependencyModules.map((mod) => mod.lib), targetModule.lib],
109
109
  };
@@ -1,7 +1,7 @@
1
1
  import { mkdtemp, rm } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import type { BackendEnv } from "akanjs/base";
4
+ import type { BackendEnv, BaseEnv } from "akanjs/base";
5
5
  import { Logger, sleep } from "akanjs/common";
6
6
  import { type AkanLib, AkanServer } from "akanjs/server";
7
7
 
@@ -12,6 +12,9 @@ const MAX_ACTIVATION_TIME = 30000;
12
12
 
13
13
  type TestDatabaseMode = "memory" | "tempFile";
14
14
 
15
+ /** `BackendEnv` carries server options only, so a harness that also stamps `process.env` needs the identity beside it. */
16
+ export type TestEnv = BaseEnv & BackendEnv;
17
+
15
18
  export interface TestServerOptions {
16
19
  workerId?: number;
17
20
  port?: number;
@@ -44,7 +47,7 @@ const resolveWorkerId = (workerId?: number) => {
44
47
  export class TestServer {
45
48
  readonly #logger = new Logger("TestServer");
46
49
  readonly #libs: AkanLib[];
47
- readonly #env: BackendEnv;
50
+ readonly #env: TestEnv;
48
51
  readonly #databaseMode: TestDatabaseMode;
49
52
  readonly #serverMode: "federation" | "batch" | "all";
50
53
  readonly #listen: boolean;
@@ -55,7 +58,7 @@ export class TestServer {
55
58
  #startAt = Date.now();
56
59
  #server?: AkanServer;
57
60
  #tempDir?: string;
58
- static initClient(env: BackendEnv, workerId?: number) {
61
+ static initClient(env: BaseEnv, workerId?: number) {
59
62
  TestServer.applyProcessEnv(env, {
60
63
  workerId,
61
64
  port: TEST_LISTEN_PORT_BASE + resolveWorkerId(workerId),
@@ -63,7 +66,7 @@ export class TestServer {
63
66
  });
64
67
  }
65
68
  static applyProcessEnv(
66
- env: BackendEnv,
69
+ env: BaseEnv,
67
70
  {
68
71
  workerId,
69
72
  port,
@@ -82,7 +85,7 @@ export class TestServer {
82
85
  process.env.AKAN_PUBLIC_SERVER_PORT = String(resolvedPort);
83
86
  process.env.SERVER_HTTP_PROTOCOL = "http:";
84
87
  }
85
- constructor(env: BackendEnv, libs: AkanLib | AkanLib[], options: TestServerOptions = {}) {
88
+ constructor(env: TestEnv, libs: AkanLib | AkanLib[], options: TestServerOptions = {}) {
86
89
  this.workerId = resolveWorkerId(options.workerId);
87
90
  this.#port = options.port ?? TEST_LISTEN_PORT_BASE + this.workerId;
88
91
  this.#env = { ...env };
@@ -8,7 +8,7 @@ export interface BaseEnv {
8
8
  operationMode: "local" | "edge" | "cloud" | "module";
9
9
  databaseMode?: DatabaseMode;
10
10
  }
11
- export type BackendEnv = BaseEnv & {
11
+ export type BackendEnv = {
12
12
  hostname?: string | null;
13
13
  port?: number;
14
14
  database?: {
@@ -11,10 +11,10 @@ export interface AdaptorOverride {
11
11
  * App/library server option builder: use objects, signal middleware, adaptor overrides, web proxies, and the
12
12
  * server settings an app owns — MCP, the agent relay's access policy, and the LLM the relay speaks to.
13
13
  */
14
- export declare class AkanOption<Env = unknown> {
14
+ export declare class AkanOption<Env extends BackendEnv = BackendEnv> {
15
15
  #private;
16
16
  constructor();
17
- use(fnOrObject: ((env: Env & BackendEnv) => Record<string, PromiseOrObject<unknown>>) | Record<string, PromiseOrObject<unknown>>): this;
17
+ use(fnOrObject: ((env: Env) => Record<string, PromiseOrObject<unknown>>) | Record<string, PromiseOrObject<unknown>>): this;
18
18
  applyMiddleware(...middlewares: MiddlewareCls[]): this;
19
19
  /** Rebinds a predefined adaptor role (e.g. `LlmAdaptorRole`) to the app's own implementation. Last writer wins. */
20
20
  applyAdaptor<T extends Adaptor>(role: AdaptorCls<T>, adaptor: AdaptorCls<T>): this;
@@ -26,18 +26,18 @@ export declare class AkanOption<Env = unknown> {
26
26
  */
27
27
  setMcp(mcp?: boolean | McpServerOption): this;
28
28
  /**
29
- * Who may spend the LLM key through the `runAgentTurn` relay the `AgentRelayAccess` policy, which defaults to
30
- * allowing everyone because the framework has no account model to gate on. `null` reopens it.
29
+ * Who may spend the LLM key through the `runAgentTurn` relay. With no policy the call is refused the same
30
+ * answer `None` gives — because the framework has no account model to gate on. `null` clears the policy.
31
31
  */
32
32
  setAgentAccess(policy: AgentRelayPolicy | null): this;
33
33
  /** Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use. */
34
34
  setLlm(llmOrFn: LlmOption | ((env: Env) => LlmOption)): this;
35
- getUses(env: Env & BackendEnv): Record<string, PromiseOrObject<unknown>>;
35
+ getUses(env: Env): Record<string, PromiseOrObject<unknown>>;
36
36
  getMiddlewares(): MiddlewareCls[];
37
37
  getAdaptorOverrides(): AdaptorOverride[];
38
38
  getWebProxies(): WebProxyRegistration[];
39
39
  getMcp(): boolean | McpServerOption | undefined;
40
40
  getAgentAccess(): AgentRelayPolicy | null | undefined;
41
- getLlm(env: Env & BackendEnv): LlmOption;
41
+ getLlm(env: Env): LlmOption;
42
42
  }
43
- export declare function createDefaultAkanOption(): AkanOption<unknown>;
43
+ export declare function createDefaultAkanOption(): AkanOption<BackendEnv>;
@@ -1,11 +1,11 @@
1
- import { type BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv, type BaseEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import type { Adaptor, AdaptorCls, DatabaseConfig, Service, ServiceCls, SolidConfig } from "akanjs/service";
4
4
  import type { ServerSignal, ServerSignalCls } from "akanjs/signal";
5
5
  import type { AkanLib, AkanLibProps } from "./akanLib.d.ts";
6
6
  import { type McpAuthOption } from "./mcp.d.ts";
7
7
  export interface AkanServerProps extends AkanLibProps {
8
- env?: BaseEnv;
8
+ env?: BackendEnv;
9
9
  prefix?: string;
10
10
  websocketPrefix?: string;
11
11
  openapi?: boolean;
@@ -68,10 +68,7 @@ export declare class AkanServer {
68
68
  readonly logger: Logger;
69
69
  readonly name: string;
70
70
  readonly libs: AkanLib[];
71
- readonly env: BaseEnv & {
72
- database?: DatabaseConfig;
73
- solid?: SolidConfig;
74
- };
71
+ readonly env: BackendEnv;
75
72
  prefix: string;
76
73
  websocketPrefix: string;
77
74
  openapi: boolean;
@@ -81,7 +78,7 @@ export declare class AkanServer {
81
78
  mcpOption: Omit<McpServerOption, "enabled" | "readOnly" | "auth">;
82
79
  serverMode: "federation" | "batch" | "all";
83
80
  shutdownTimeoutMs: number;
84
- constructor(name?: string, env?: {}, serverMode?: "federation" | "batch" | "all", ...libsOrOptions: (AkanLib | AkanServerOptions)[]);
81
+ constructor(name?: string, env?: BackendEnv, serverMode?: "federation" | "batch" | "all", ...libsOrOptions: (AkanLib | AkanServerOptions)[]);
85
82
  setPrefix(prefix: string): this;
86
83
  setWebsocketPrefix(websocketPrefix: string): this;
87
84
  setOpenApi(openapi?: boolean): this;
@@ -1,4 +1,4 @@
1
- import type { BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import { type Adaptor, type AdaptorCls, type Service, type ServiceCls, type WebsocketAdaptor } from "akanjs/service";
4
4
  import { type MiddlewareCls } from "../../signal/middleware.d.ts";
@@ -31,7 +31,7 @@ export declare class DiLifecycle {
31
31
  adaptor: ReadonlyMap<string, AdaptorCls>;
32
32
  middleware: ReadonlyMap<string, MiddlewareCls>;
33
33
  };
34
- constructor(env: BaseEnv, serverMode: "federation" | "batch" | "all", ...libs: AkanLib[]);
34
+ constructor(env: BackendEnv, serverMode: "federation" | "batch" | "all", ...libs: AkanLib[]);
35
35
  /** Run every init stage in dependency order and collect the generated routes. */
36
36
  initializeAll(): Promise<SignalRoutes>;
37
37
  destroyAll(): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { type BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import type { InjectRegistry, LiveRegistry } from "akanjs/service";
4
4
  import { type McpExposedEndpoint, type McpToolResult, type PromptMessage } from "../../signal/mcp.d.ts";
@@ -19,7 +19,7 @@ export declare class McpPromptError extends Error {
19
19
  }
20
20
  interface McpDispatcherProps {
21
21
  registry: InjectRegistry;
22
- env: BaseEnv;
22
+ env: BackendEnv;
23
23
  live: LiveRegistry;
24
24
  middleware: Map<string, MiddlewareCls>;
25
25
  /** The one language error text is resolved in, matching the catalogue the client was handed. */
@@ -1,4 +1,4 @@
1
- import type { BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import type { InjectRegistry, LiveRegistry } from "akanjs/service";
4
4
  import type { MiddlewareCls } from "../../signal/middleware.d.ts";
@@ -6,7 +6,8 @@ import type { HttpRoutes } from "../types.d.ts";
6
6
  import { type McpAuthOption } from "./McpAuth.d.ts";
7
7
  export interface McpRouterProps {
8
8
  registry: InjectRegistry;
9
- env: BaseEnv;
9
+ /** Spread into every `SignalContext` this router's dispatcher builds; middleware reads it. */
10
+ env: BackendEnv;
10
11
  live: LiveRegistry;
11
12
  middleware: Map<string, MiddlewareCls>;
12
13
  path?: string;
@@ -1,4 +1,4 @@
1
- import { type BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv } from "akanjs/base";
2
2
  import { Logger } from "akanjs/common";
3
3
  import { type InjectRegistry, type LiveRegistry, type WebsocketAdaptor } from "akanjs/service";
4
4
  import { type Endpoint, type EndpointCls } from "../../signal/endpoint.d.ts";
@@ -26,7 +26,7 @@ export declare class SignalResolver {
26
26
  static resolveSlice(sliceCls: SliceCls): EndpointCls;
27
27
  static resolveEndpoint(endpointCls: EndpointCls, endpoint: Endpoint, { registry, env, live, middleware, }: {
28
28
  registry: InjectRegistry;
29
- env: BaseEnv;
29
+ env: BackendEnv;
30
30
  live: LiveRegistry;
31
31
  middleware: Map<string, MiddlewareCls>;
32
32
  }): SignalRoutes;
@@ -1,4 +1,4 @@
1
- import { type BaseEnv } from "akanjs/base";
1
+ import { type BackendEnv } from "akanjs/base";
2
2
  import { type ConstantFieldTypeInput, type FieldToValue, type PlainTypeToFieldType } from "akanjs/constant";
3
3
  import type { DatabaseModel } from "akanjs/document";
4
4
  import type { Endpoint, EndpointCls, Internal, InternalCls, ServerSignal, ServerSignalCls, SliceCls } from "akanjs/signal";
@@ -60,7 +60,7 @@ export declare class InjectInfo<Type extends InjectType = any, ReturnType = any,
60
60
  readonly cacheOption?: CacheSetOptions;
61
61
  readonly parentRefName: string;
62
62
  constructor(type: Type, options: InjectBuilderOptions<ReturnType>);
63
- static resolveInjection(instance: Adaptor | Service, applyCls: AdaptorCls | ServiceCls, registry: InjectRegistry, env: BaseEnv): Promise<void>;
63
+ static resolveInjection(instance: Adaptor | Service, applyCls: AdaptorCls | ServiceCls, registry: InjectRegistry, env: BackendEnv): Promise<void>;
64
64
  }
65
65
  type GetFieldValue<ValueRef, ExplicitType, MapValue = never> = unknown extends ExplicitType ? FieldToValue<ValueRef, MapValue> : ExplicitType;
66
66
  export declare const injectionBuilder: (parentRefName: string) => {
@@ -1,6 +1,6 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import type { Client as LibsqlClient } from "@libsql/client";
3
- import { type BaseEnv, type PromiseOrObject } from "akanjs/base";
3
+ import { type PromiseOrObject } from "akanjs/base";
4
4
  import { type ConstantModel } from "akanjs/constant";
5
5
  import { type DatabaseModel, type DocumentQuery, type DocumentSchema, type DocumentUpdate, type DocumentUpdateInput, type DocumentUpdateOperator, type DocumentUpdateOptions, type SchemaOf } from "akanjs/document";
6
6
  import type { Sql } from "postgres";
@@ -120,7 +120,7 @@ export interface DatabaseAdaptor {
120
120
  transaction<T>(fn: () => PromiseOrObject<T>): Promise<T>;
121
121
  getSearchIndex(): SearchIndex | null;
122
122
  }
123
- interface SqliteEnv extends BaseEnv {
123
+ interface SqliteEnv {
124
124
  workspaceRoot?: string;
125
125
  database?: DatabaseConfig;
126
126
  }
@@ -1,4 +1,3 @@
1
- import type { BaseEnv } from "akanjs/base";
2
1
  import type { AkanJob, AkanJobOptions, AkanWorker } from "../ipcTypes.d.ts";
3
2
  type QueueLike = {
4
3
  add(name: string, args: unknown[], options?: AkanJobOptions): Promise<unknown>;
@@ -10,7 +9,9 @@ export interface QueueAdaptor {
10
9
  }
11
10
  declare const BullQueue_base: import("..").AdaptorCls<{}, {
12
11
  redis: import("..").InjectInfo<"plug", import("ioredis").default, never, never>;
13
- prefix: import("..").InjectInfo<"env", string, BaseEnv, never>;
12
+ prefix: import("..").InjectInfo<"env", string, {
13
+ [key: string]: any;
14
+ }, never>;
14
15
  }>;
15
16
  export declare class BullQueue extends BullQueue_base implements QueueAdaptor {
16
17
  #private;
@@ -3,8 +3,12 @@ import type { QueueAdaptor } from "./queue.adaptor";
3
3
  import { type SolidConfig, type SolidEnv } from "./solidSqlite.d.ts";
4
4
  declare const SolidQueue_base: import("..").AdaptorCls<{}, {
5
5
  config: import("..").InjectInfo<"env", Required<SolidConfig>, SolidEnv, never>;
6
- queueName: import("..").InjectInfo<"env", string, SolidEnv, never>;
7
- workerId: import("..").InjectInfo<"env", string, SolidEnv, never>;
6
+ queueName: import("..").InjectInfo<"env", string, {
7
+ [key: string]: any;
8
+ }, never>;
9
+ workerId: import("..").InjectInfo<"env", string, {
10
+ [key: string]: any;
11
+ }, never>;
8
12
  }>;
9
13
  export declare class SolidQueue extends SolidQueue_base implements QueueAdaptor {
10
14
  #private;
@@ -1,5 +1,5 @@
1
1
  import { Database } from "bun:sqlite";
2
- import type { BaseEnv, Dayjs } from "akanjs/base";
2
+ import { type Dayjs } from "akanjs/base";
3
3
  export interface SolidConfig {
4
4
  filePath?: string;
5
5
  journalMode?: "WAL" | "DELETE" | "TRUNCATE" | "PERSIST" | "MEMORY" | "OFF";
@@ -9,7 +9,7 @@ export interface SolidConfig {
9
9
  queuePollIntervalMs?: number;
10
10
  queueLeaseMs?: number;
11
11
  }
12
- export interface SolidEnv extends BaseEnv {
12
+ export interface SolidEnv {
13
13
  workspaceRoot?: string;
14
14
  solid?: SolidConfig;
15
15
  }
@@ -1,12 +1,7 @@
1
- import { Any } from "akanjs/base";
2
1
  import { AgentTurn } from "./agentTurn.d.ts";
3
2
  declare const AgentInternal_base: import("./internal.d.ts").InternalCls<import("akanjs/service").ServiceModel<typeof import("akanjs/service").AgentService, any, any, {
4
3
  agentService: import("akanjs/service").AgentService;
5
- }>, {
6
- warnOpenRelay: import("./internalInfo.d.ts").InternalInfo<"init", {
7
- agentService: import("akanjs/service").AgentService;
8
- }, [], [], [], typeof Any, void, false>;
9
- }>;
4
+ }>, {}>;
10
5
  export declare class AgentInternal extends AgentInternal_base {
11
6
  }
12
7
  declare const AgentEndpoint_base: import("./endpoint.d.ts").EndpointCls<import("akanjs/service").ServiceModel<typeof import("akanjs/service").AgentService, any, any, {
@@ -15,8 +15,8 @@ export type AgentRelayPolicy = (context: SignalContext) => boolean | Promise<boo
15
15
  * Gate for the `runAgentTurn` relay. Every tool runs in the caller's own browser session, so the LLM key is the
16
16
  * one thing this endpoint spends — ungated, any visitor can bill the app's provider through fetch alone.
17
17
  *
18
- * The framework has no account model to gate on, so with no policy registered it allows everyonewhat `Public`
19
- * said before, but with a seam: a product hardens it at boot, e.g.
18
+ * The framework has no account model to gate on, so with no policy registered it refuses every call the same
19
+ * answer `None` gives. An app opens it at boot, e.g.
20
20
  * `AgentRelayAccess.use((context) => !!context.get("account"))`. The policy is the app's; the framework cannot know it.
21
21
  */
22
22
  export declare class AgentRelayAccess implements Guard {
@@ -25,7 +25,5 @@ export declare class AgentRelayAccess implements Guard {
25
25
  static scope: GuardScope;
26
26
  static use(policy: AgentRelayPolicy | null): void;
27
27
  static get hasPolicy(): boolean;
28
- /** Runs at boot, after the app entry has evaluated — a module-scope check would fire before `use()` could. */
29
- static warnIfOpen(): void;
30
28
  canPass(context: SignalContext): Promise<boolean>;
31
29
  }
@@ -1,6 +1,6 @@
1
- import type { BaseEnv, Cls, PromiseOrObject } from "akanjs/base";
1
+ import type { BackendEnv, Cls, PromiseOrObject } from "akanjs/base";
2
2
  import type { SignalContext } from "./signalContext.d.ts";
3
- export interface Middleware<Env extends BaseEnv = BaseEnv> {
3
+ export interface Middleware<Env extends BackendEnv = BackendEnv> {
4
4
  use(env: Env): PromiseOrObject<(context: SignalContext, next: () => Promise<unknown>) => PromiseOrObject<unknown>>;
5
5
  }
6
6
  export type MiddlewareCls = Cls<Middleware, {
@@ -8,13 +8,13 @@ export type MiddlewareCls = Cls<Middleware, {
8
8
  }>;
9
9
  export declare const middleware: (refName: string) => {
10
10
  new (): {
11
- use(env: BaseEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
11
+ use(env: BackendEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
12
12
  };
13
13
  refName: string;
14
14
  };
15
15
  declare const Logging_base: {
16
16
  new (): {
17
- use(env: BaseEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
17
+ use(env: BackendEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
18
18
  };
19
19
  refName: string;
20
20
  };
@@ -23,7 +23,7 @@ export declare class Logging extends Logging_base {
23
23
  }
24
24
  declare const Cache_base: {
25
25
  new (): {
26
- use(env: BaseEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
26
+ use(env: BackendEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
27
27
  };
28
28
  refName: string;
29
29
  };
@@ -32,7 +32,7 @@ export declare class Cache extends Cache_base {
32
32
  }
33
33
  declare const Timeout_base: {
34
34
  new (): {
35
- use(env: BaseEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
35
+ use(env: BackendEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
36
36
  };
37
37
  refName: string;
38
38
  };
@@ -41,7 +41,7 @@ export declare class Timeout extends Timeout_base {
41
41
  }
42
42
  declare const Retry_base: {
43
43
  new (): {
44
- use(env: BaseEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
44
+ use(env: BackendEnv): Promise<(context: SignalContext, next: () => Promise<unknown>) => Promise<unknown>>;
45
45
  };
46
46
  refName: string;
47
47
  };
@@ -1,4 +1,4 @@
1
- import { type BaseEnv, type PromiseOrObject } from "akanjs/base";
1
+ import { type BackendEnv, type PromiseOrObject } from "akanjs/base";
2
2
  import { type ConstantFieldTypeInput } from "akanjs/constant";
3
3
  import type { Adaptor, AdaptorCls, DatabaseService, InjectRegistry, LiveRegistry } from "akanjs/service";
4
4
  import type { Internal, InternalInfo, MiddlewareCls } from ".";
@@ -11,7 +11,7 @@ interface WebSocketRequest {
11
11
  eventType: WebSocketEventType;
12
12
  }
13
13
  type RuntimeRecord = Record<string, unknown>;
14
- export declare class SignalContext<Ctx extends HttpExecutionContext | WebSocketExecutionContext = HttpExecutionContext | WebSocketExecutionContext, Env extends BaseEnv = BaseEnv> {
14
+ export declare class SignalContext<Ctx extends HttpExecutionContext | WebSocketExecutionContext = HttpExecutionContext | WebSocketExecutionContext, Env extends BackendEnv = BackendEnv> {
15
15
  #private;
16
16
  key: string;
17
17
  transport: SignalTransportType;
@@ -1,4 +1,4 @@
1
1
  export { sample } from "./sample.d.ts";
2
2
  export { sampleOf } from "./sampleOf.d.ts";
3
3
  export { configureSignalTest, getOrSetupSignalTestContext, getOrSetupSignalTestFetch, getSignalTestContext, getSignalTestFetch, hasSignalTestContext, type SignalTestContext, type SignalTestOptions, type SignalTestTarget, setupSignalTestTarget, terminateSignalTestContext, } from "./signalTestRuntime.d.ts";
4
- export { TestServer, type TestServerOptions } from "./testServer.d.ts";
4
+ export { type TestEnv, TestServer, type TestServerOptions } from "./testServer.d.ts";
@@ -1,11 +1,10 @@
1
- import type { BackendEnv } from "akanjs/base";
2
1
  import type { FetchProxy } from "akanjs/fetch";
3
2
  import type { AkanLib } from "akanjs/server";
4
- import { type TestServerOptions } from "./testServer.d.ts";
3
+ import { type TestEnv, type TestServerOptions } from "./testServer.d.ts";
5
4
  export interface SignalTestTarget {
6
5
  type: "app" | "lib";
7
6
  name: string;
8
- env: BackendEnv;
7
+ env: TestEnv;
9
8
  fetch: FetchProxy;
10
9
  libs: AkanLib[];
11
10
  }
@@ -1,6 +1,8 @@
1
- import type { BackendEnv } from "akanjs/base";
1
+ import type { BackendEnv, BaseEnv } from "akanjs/base";
2
2
  import { type AkanLib } from "akanjs/server";
3
3
  type TestDatabaseMode = "memory" | "tempFile";
4
+ /** `BackendEnv` carries server options only, so a harness that also stamps `process.env` needs the identity beside it. */
5
+ export type TestEnv = BaseEnv & BackendEnv;
4
6
  export interface TestServerOptions {
5
7
  workerId?: number;
6
8
  port?: number;
@@ -12,13 +14,13 @@ export interface TestServerOptions {
12
14
  export declare class TestServer {
13
15
  #private;
14
16
  workerId: number;
15
- static initClient(env: BackendEnv, workerId?: number): void;
16
- static applyProcessEnv(env: BackendEnv, { workerId, port, serverMode, }?: {
17
+ static initClient(env: BaseEnv, workerId?: number): void;
18
+ static applyProcessEnv(env: BaseEnv, { workerId, port, serverMode, }?: {
17
19
  workerId?: number;
18
20
  port?: number;
19
21
  serverMode?: "federation" | "batch" | "all";
20
22
  }): void;
21
- constructor(env: BackendEnv, libs: AkanLib | AkanLib[], options?: TestServerOptions);
23
+ constructor(env: TestEnv, libs: AkanLib | AkanLib[], options?: TestServerOptions);
22
24
  init(): Promise<void>;
23
25
  cleanup(): Promise<void>;
24
26
  terminate(): Promise<void>;