@gobing-ai/ts-infra 0.4.7 → 0.4.9

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.
package/README.md CHANGED
@@ -704,6 +704,53 @@ const app = await runNodeApplication({
704
704
  });
705
705
  ```
706
706
 
707
+ ### System Events — lifecycle bus propagation contract
708
+
709
+ `runApplication` creates a single `lifecycleBus` (`EventBus<BusLifecycleEvents>`)
710
+ and parents the application event bus to it. Downstream consumers join the same
711
+ stream when callers pass `app.lifecycleBus` into their constructors. Domain emits
712
+ fan out as `bus.*` lifecycle events on the parent and — when a file observer is
713
+ attached — land in the JSONL System Events log alongside the domain event itself.
714
+
715
+ **Six System Events prefixes and where they originate:**
716
+
717
+ | Prefix | Source | Constructed when `events` is omitted |
718
+ |--------|--------|---------------------------------------|
719
+ | `bus.*` | `EventBus` lifecycle self-observability (the `lifecycleBus` itself) | always — `runApplication` creates it |
720
+ | `api.*` | `APIClient` (`api.request.error`) | `new EventBus<ApiClientEvents>({ lifecycleBus })` (R5) |
721
+ | `rule.*` | `RuleEngine` (rule-run structured observability) | `new EventBus<RuleEngineEvents>({ lifecycleBus })` (R2) |
722
+ | `workflow.*` | `WorkflowService` (workflow-run observability) | `new EventBus<WorkflowEngineEvents>({ lifecycleBus })` via `resolveEvents` (R3) |
723
+ | `agent.*` | `AiRunner` / `TeamOrchestrator` (agent invocation observability) | `new EventBus<AgentEvents>({ lifecycleBus })` (R4) |
724
+ | `process.*` | `AiRunner` default-executor process events | `new EventBus<AiRunnerProcessEvents>({ lifecycleBus })` (R4) |
725
+
726
+ **Caller-supplied `events` always wins.** Every consumer accepts an explicit
727
+ `events?: EventBus<TEvents>` (or equivalent) and an optional `lifecycleBus?`.
728
+ When `events` is provided the consumer uses it as-is; when `events` is omitted
729
+ and `lifecycleBus` is provided, the consumer constructs an internal bus
730
+ parented to the lifecycle bus. This keeps standalone use zero-config and lets
731
+ the bootstrap opt every consumer into the System Events stream by passing a
732
+ single `lifecycleBus`.
733
+
734
+ **Portable vs Node subpath split (ADR-011).** `runApplication` (portable)
735
+ never opens files: it calls `attachFileObserver(lifecycleBus, filePath, writer)`
736
+ only when `services.fileObserverWriter` is provided; its portable default path is
737
+ `logs/system-events.jsonl`. The Node subpath `runNodeApplication` constructs a
738
+ Node writer and uses `createNodeFileSystem()` to resolve the path to
739
+ `<projectRoot>/logs/system-events.jsonl`. Set `config.events.fileObserver: false`
740
+ to disable; supply a custom `filePath` (or a test-stub writer via
741
+ `services.fileObserverWriter`) to override.
742
+
743
+ **Wiring a consumer manually.** When you construct a consumer outside the
744
+ bootstrap (or want to share the bootstrap's bus), pass the exposed
745
+ `app.lifecycleBus`:
746
+
747
+ ```ts
748
+ import { RuleEngine } from '@gobing-ai/ts-rule-engine';
749
+
750
+ const engine = new RuleEngine({ lifecycleBus: app.lifecycleBus });
751
+ // engine now emits `rule.*` events into the same JSONL log as the bootstrap.
752
+ ```
753
+
707
754
  If you prefer manual wiring (e.g. you already have an init sequence), the individual
708
755
  subsystems still work standalone — `EventBus`, `APIClient`, `getLogger`, etc. are
709
756
  all importable directly from the main barrel and do not require the bootstrap.
@@ -1,4 +1,4 @@
1
- import type { EventBus } from './event-bus/event-bus';
1
+ import { type BusLifecycleEvents, EventBus } from './event-bus/index';
2
2
  import type { ApiClientEvents } from './events';
3
3
  /** Configuration for {@link APIClient}: base URL, default headers, timeout, and optional custom fetch. */
4
4
  export interface APIClientConfig {
@@ -12,6 +12,13 @@ export interface APIClientConfig {
12
12
  * returns non-2xx statuses normally, so it emits only on network/timeout.
13
13
  */
14
14
  events?: EventBus<ApiClientEvents>;
15
+ /**
16
+ * Optional lifecycle bus to bridge `api.*` events into the application
17
+ * System Events stream (R5). When `events` is omitted the client
18
+ * constructs an internal `EventBus<ApiClientEvents>` parented to this
19
+ * bus so `api.request.error` appears in the JSONL log.
20
+ */
21
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
15
22
  }
16
23
  /** Per-request overrides: headers, timeout, operation name, and abort signal. */
17
24
  export interface RequestOptions {
@@ -46,9 +53,7 @@ export declare class APIError extends Error {
46
53
  /**
47
54
  * Typed HTTP client builder wrapping fetch with automatic JSON serialization,
48
55
  * timeout support, and OpenTelemetry tracing on every request.
49
- *
50
56
  * @example
51
- * ```ts
52
57
  * const client = new APIClient({ baseUrl: 'https://api.example.com' });
53
58
  * const data = await client.get<User>('/users/1');
54
59
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAUhD,0GAA0G;AAC1G,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACtC;AACD,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,mHAAmH;AACnH,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACrD,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AACD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,KAAK;aAEX,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAKnC;AAoED;;;;;;;;;GASG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;gBAEnD,MAAM,EAAE,eAAe;IAQnC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,gBAAgB;IASxB;;;;OAIG;YACW,UAAU;YAkGV,OAAO;IAmCrB;;;;;;;OAOG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA+C3G,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvD,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIzE,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAGnE"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { SpanKind } from '@opentelemetry/api';
5
5
  import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL, } from '@opentelemetry/semantic-conventions';
6
+ import { EventBus } from './event-bus/index.js';
6
7
  import { getHttpClientRequestDuration, getHttpClientRequestErrors, getHttpClientRequestTotal, } from './telemetry/metrics.js';
7
8
  import { traceAsync } from './telemetry/tracing.js';
8
9
  /**
@@ -82,9 +83,7 @@ async function readBodyCapped(response, maxResponseBytes) {
82
83
  /**
83
84
  * Typed HTTP client builder wrapping fetch with automatic JSON serialization,
84
85
  * timeout support, and OpenTelemetry tracing on every request.
85
- *
86
86
  * @example
87
- * ```ts
88
87
  * const client = new APIClient({ baseUrl: 'https://api.example.com' });
89
88
  * const data = await client.get<User>('/users/1');
90
89
  * ```
@@ -100,7 +99,9 @@ export class APIClient {
100
99
  this.defaultHeaders = config.defaultHeaders ?? {};
101
100
  this.timeout = config.timeout ?? 30_000;
102
101
  this.fetchFn = config.fetch ?? globalThis.fetch;
103
- this.events = config.events;
102
+ this.events =
103
+ config.events ??
104
+ (config.lifecycleBus ? new EventBus({ lifecycleBus: config.lifecycleBus }) : undefined);
104
105
  }
105
106
  buildUrl(path) {
106
107
  return `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
@@ -19,6 +19,12 @@ import type { ApplicationBootstrapOptions, ApplicationRuntime } from './types';
19
19
  * Accepts injected dependencies; never opens files, reads config from disk,
20
20
  * or wires runtime-specific exporters.
21
21
  *
22
+ * The returned `lifecycleBus` is the parent of the application `events` bus and
23
+ * can be passed to RuleEngine, WorkflowService, AiRunner, TeamOrchestrator, and
24
+ * APIClient so their domain events share one System Events stream. A supplied
25
+ * file-observer writer records that stream as JSONL; the Node subpath supplies
26
+ * the writer and default `logs/system-events.jsonl` path.
27
+ *
22
28
  * Startup is plugin-driven. Built-in service plugins are registered in dependency
23
29
  * order, then `loadAll()` + `startAll()` run them forward:
24
30
  * 1. Resolve bootstrap config; build EventBus + PluginHost
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAiCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,cAAc,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EAC7F,OAAO,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1D,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAmIlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,YAAY,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAiCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,cAAc,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EAC7F,OAAO,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1D,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAyJlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,YAAY,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { attachDefaultObservers, createLifecycleBus } from '../event-bus/default-observers.js';
13
13
  import { EventBus } from '../event-bus/event-bus.js';
14
+ import { attachFileObserver } from '../event-bus/file-observer.js';
14
15
  import { getLogger } from '../logger.js';
15
16
  import { initScheduler } from '../scheduler/factory.js';
16
17
  import { loggerPlugin, schedulerPlugin, telemetryPlugin, userCallbackPlugin } from './plugins/builtins.js';
@@ -39,6 +40,12 @@ async function performShutdown(state, reason) {
39
40
  * Accepts injected dependencies; never opens files, reads config from disk,
40
41
  * or wires runtime-specific exporters.
41
42
  *
43
+ * The returned `lifecycleBus` is the parent of the application `events` bus and
44
+ * can be passed to RuleEngine, WorkflowService, AiRunner, TeamOrchestrator, and
45
+ * APIClient so their domain events share one System Events stream. A supplied
46
+ * file-observer writer records that stream as JSONL; the Node subpath supplies
47
+ * the writer and default `logs/system-events.jsonl` path.
48
+ *
42
49
  * Startup is plugin-driven. Built-in service plugins are registered in dependency
43
50
  * order, then `loadAll()` + `startAll()` run them forward:
44
51
  * 1. Resolve bootstrap config; build EventBus + PluginHost
@@ -91,6 +98,9 @@ export async function runApplication(options) {
91
98
  const eventsEnabled = options.config?.events?.enabled ?? true;
92
99
  const eventsLifecycle = options.config?.events?.lifecycle ?? true;
93
100
  const eventsDefaultObservers = options.config?.events?.defaultObservers ?? true;
101
+ const eventsFileObserver = options.config?.events?.fileObserver ?? true;
102
+ const fileObserverWriter = options.services?.fileObserverWriter;
103
+ const eventsFilePath = options.config?.events?.filePath ?? (fileObserverWriter !== undefined ? 'logs/system-events.jsonl' : undefined);
94
104
  const state = {
95
105
  app: undefined,
96
106
  stopped: false,
@@ -105,6 +115,18 @@ export async function runApplication(options) {
105
115
  if (lifecycleBus && eventsDefaultObservers) {
106
116
  attachDefaultObservers(lifecycleBus);
107
117
  }
118
+ // Attach the JSONL file observer when a writer is available. The
119
+ // portable subpath never opens files (ADR-011): the Node subpath
120
+ // injects `createNodeFileSystem()` (or a test stub) and resolves the
121
+ // default path against the project root. Without a writer or path
122
+ // the observer is a no-op even if `events.fileObserver` is `true`.
123
+ let fileObserverAttached = false;
124
+ let fileObserverPath;
125
+ if (lifecycleBus && eventsFileObserver && eventsFilePath !== undefined && fileObserverWriter !== undefined) {
126
+ attachFileObserver(lifecycleBus, eventsFilePath, fileObserverWriter);
127
+ fileObserverAttached = true;
128
+ fileObserverPath = eventsFilePath;
129
+ }
108
130
  const events = options.services?.events ??
109
131
  options.config?.events?.bus ??
110
132
  new EventBus({ lifecycleBus: lifecycleBus });
@@ -128,7 +150,13 @@ export async function runApplication(options) {
128
150
  // ── Build runtime handle (before startAll so plugins can capture it) ─
129
151
  const resolvedConfig = {
130
152
  logging: loggingConfig,
131
- events: { enabled: eventsEnabled, lifecycle: eventsLifecycle, defaultObservers: eventsDefaultObservers },
153
+ events: {
154
+ enabled: eventsEnabled,
155
+ lifecycle: eventsLifecycle,
156
+ defaultObservers: eventsDefaultObservers,
157
+ fileObserver: fileObserverAttached,
158
+ ...(fileObserverPath !== undefined ? { filePath: fileObserverPath } : {}),
159
+ },
132
160
  telemetry: telemetryConfig,
133
161
  scheduler: schedulerConfig,
134
162
  };
@@ -8,6 +8,7 @@
8
8
  * @module application/types
9
9
  */
10
10
  import type { EventBus } from '../event-bus/event-bus';
11
+ import type { FileObserverWriter } from '../event-bus/file-observer';
11
12
  import type { BusLifecycleEvents, EventMap } from '../event-bus/types';
12
13
  import type { InfraEvents } from '../events';
13
14
  import type { Logger, LogLevel } from '../logger';
@@ -38,6 +39,20 @@ export interface EventsOptions<TEvents extends EventMap = InfraEvents> {
38
39
  lifecycle?: boolean;
39
40
  /** Attach default observers (log + telemetry). Default `true`. */
40
41
  defaultObservers?: boolean;
42
+ /**
43
+ * Attach a file observer that appends JSONL rows for every lifecycle
44
+ * event to `filePath`. Default `true` when a writer is available
45
+ * (Node subpath injects `createNodeFileSystem()`; portable callers must
46
+ * supply one via `services.fileObserverWriter`). No-op when `lifecycle`
47
+ * is disabled or no writer is present.
48
+ */
49
+ fileObserver?: boolean;
50
+ /**
51
+ * JSONL output path. Defaults to `logs/system-events.jsonl` when a writer
52
+ * is supplied; the Node subpath resolves this against the project root.
53
+ * Portable callers may override it with an absolute path.
54
+ */
55
+ filePath?: string;
41
56
  /** Pre-built event bus (skips creation when provided). */
42
57
  bus?: EventBus<TEvents>;
43
58
  }
@@ -75,6 +90,10 @@ export interface ApplicationBootstrapConfig {
75
90
  enabled: boolean;
76
91
  lifecycle: boolean;
77
92
  defaultObservers: boolean;
93
+ /** Whether the JSONL file observer was attached (false when no writer is available). */
94
+ fileObserver: boolean;
95
+ /** Resolved JSONL absolute path, or undefined when fileObserver is false. */
96
+ filePath?: string;
78
97
  };
79
98
  readonly telemetry: {
80
99
  enabled: boolean;
@@ -92,6 +111,13 @@ export interface ApplicationServices<TEvents extends EventMap = InfraEvents> {
92
111
  logger?: Logger;
93
112
  events?: EventBus<TEvents>;
94
113
  lifecycleBus?: EventBus<BusLifecycleEvents>;
114
+ /**
115
+ * Writer for `attachFileObserver`. Portable subpath never opens files
116
+ * (ADR-011); the Node subpath injects `createNodeFileSystem()`. When
117
+ * absent, the file observer is skipped even if `events.fileObserver`
118
+ * is `true`.
119
+ */
120
+ fileObserverWriter?: FileObserverWriter;
95
121
  db?: DbAdapterLike;
96
122
  scheduler?: SchedulerAdapter;
97
123
  /** Pre-built plugin host (when injecting instead of constructing). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/application/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/application/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAmGlD"}
1
+ {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAgIlD"}
@@ -17,7 +17,7 @@
17
17
  import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
18
18
  import { dirname } from 'node:path';
19
19
  import { createDbAdapter } from '@gobing-ai/ts-db';
20
- import { interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
20
+ import { createNodeFileSystem, interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
21
21
  import { runApplication } from './application/index.js';
22
22
  import { dbPlugin } from './application/plugins/builtins.js';
23
23
  import { NodeSchedulerAdapter } from './scheduler-node.js';
@@ -160,10 +160,12 @@ export async function runNodeApplication(options) {
160
160
  const yamlLog = yamlBootstrap.logging;
161
161
  const yamlTel = yamlBootstrap.telemetry;
162
162
  const yamlSched = yamlBootstrap.scheduler;
163
+ const yamlEvents = yamlBootstrap.events;
163
164
  const databaseOpts = (yamlBootstrap.database ?? {});
164
165
  const loggingOpts = { ...yamlLog, ...options.config?.logging };
165
166
  const telemetryOpts = { ...yamlTel, ...options.config?.telemetry };
166
167
  const schedulerOpts = { ...yamlSched, ...options.config?.scheduler };
168
+ const eventsOpts = { ...yamlEvents, ...options.config?.events };
167
169
  const logFilePath = yamlBootstrap.logging?.filePath;
168
170
  const loggingConfig = typeof logFilePath === 'string' ? { ...loggingOpts, fileSink: createFileSink(logFilePath) } : loggingOpts;
169
171
  // ── Scheduler adapter ───────────────────────────────────────────────
@@ -218,14 +220,40 @@ export async function runNodeApplication(options) {
218
220
  `(expected "bun-sqlite"). Provide a supported driver or inject a DbAdapter via services.db.`);
219
221
  }
220
222
  }
223
+ // ── Inject Node file observer (ADR-011 writer for the JSONL bus log) ──
224
+ // The portable `runApplication` never opens files; the Node subpath
225
+ // supplies the writer and resolves the default path against the project
226
+ // root. Caller-provided `services.fileObserverWriter` and
227
+ // `config.events.filePath` overrides win; `fileObserver: false` disables.
228
+ const injectedFileSystem = createNodeFileSystem();
229
+ const fileObserverWriter = options.services?.fileObserverWriter ?? {
230
+ ensureDir: (dir) => {
231
+ mkdirSync(dir, { recursive: true });
232
+ },
233
+ appendFile: (path, content) => {
234
+ appendFileSync(path, content);
235
+ },
236
+ };
237
+ const eventsFilePath = eventsOpts.filePath ?? injectedFileSystem.resolve('logs', 'system-events.jsonl');
238
+ const eventsConfig = {
239
+ ...eventsOpts,
240
+ filePath: eventsFilePath,
241
+ ...(eventsOpts.fileObserver === false ? {} : { fileObserver: true }),
242
+ };
221
243
  // ── Delegate to portable runApplication ─────────────────────────────
222
244
  // Node-specific cleanup is handled by plugins in the service ring —
223
245
  // node-telemetry onStop, owned-db onStop. No manual try/catch or stop
224
246
  // override needed.
225
247
  return await runApplication({
226
- config: { ...options.config, logging: loggingConfig, telemetry: telemetryOpts, scheduler: schedulerConfig },
248
+ config: {
249
+ ...options.config,
250
+ logging: loggingConfig,
251
+ telemetry: telemetryOpts,
252
+ scheduler: schedulerConfig,
253
+ events: eventsConfig,
254
+ },
227
255
  appConfig: loadedAppConfig,
228
- services: { ...options.services, ...(dbAdapter ? { db: dbAdapter } : {}) },
256
+ services: { ...options.services, fileObserverWriter, ...(dbAdapter ? { db: dbAdapter } : {}) },
229
257
  start: options.start,
230
258
  stop: options.stop,
231
259
  plugins: plugins.length ? plugins : undefined,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { APIClient, type APIClientConfig, APIError, type RawHttpResponse, type RawRequestOptions, type RequestOptions, } from './api-client';
2
- export { type AsyncEventJobPayload, attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, createLifecycleBus, EventBus, type EventMap, type FileObserverWriter, type SubscribeOptions, } from './event-bus/index';
2
+ export { type AsyncEventJobPayload, attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, type BusLifecycleEvents, createLifecycleBus, EventBus, type EventMap, type FileObserverWriter, type SubscribeOptions, } from './event-bus/index';
3
3
  export type { ApiClientEvents, ApiRequestErrorDetail, DbConnectionErrorDetail, DbEvents, InfraEvents, QueueEvents, QueueJobFailedDetail, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, } from './events';
4
4
  export type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig, QueueStats, } from './job-queue/index';
5
5
  export { getLogger, type InitLoggerOptions, initializeLogger, type Logger, type LogLevel, setLoggerMuted, } from './logger';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,SAAS,EACT,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACH,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACxB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EACR,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,0BAA0B,GAC7B,MAAM,UAAU,CAAC;AAElB,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,SAAS,EACT,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,cAAc,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACH,cAAc,EACd,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,SAAS,EACT,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACH,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACxB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EACR,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,0BAA0B,GAC7B,MAAM,UAAU,CAAC;AAElB,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,SAAS,EACT,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,cAAc,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACH,cAAc,EACd,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-infra",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "@gobing-ai/ts-infra — Infrastructure backbone: event bus, job queue, scheduler, telemetry, API client, and logging.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -78,12 +78,12 @@
78
78
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-infra-v<version> && git push --tags' && exit 1"
79
79
  },
80
80
  "dependencies": {
81
- "@gobing-ai/ts-utils": "^0.4.7",
81
+ "@gobing-ai/ts-utils": "^0.4.9",
82
82
  "@logtape/logtape": "^2.0.0"
83
83
  },
84
84
  "peerDependencies": {
85
- "@gobing-ai/ts-db": "^0.4.7",
86
- "@gobing-ai/ts-runtime": "^0.4.7",
85
+ "@gobing-ai/ts-db": "^0.4.9",
86
+ "@gobing-ai/ts-runtime": "^0.4.9",
87
87
  "@opentelemetry/api": "^1.9.0",
88
88
  "@opentelemetry/sdk-trace-node": "^2.0.0",
89
89
  "@opentelemetry/sdk-metrics": "^2.0.0",
@@ -116,8 +116,8 @@
116
116
  }
117
117
  },
118
118
  "devDependencies": {
119
- "@gobing-ai/ts-db": "^0.4.7",
120
- "@gobing-ai/ts-runtime": "^0.4.7",
119
+ "@gobing-ai/ts-db": "^0.4.9",
120
+ "@gobing-ai/ts-runtime": "^0.4.9",
121
121
  "@types/bun": "1.3.14",
122
122
  "@opentelemetry/api": "^1.9.0",
123
123
  "@opentelemetry/sdk-trace-node": "^2.0.0",
package/src/api-client.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  ATTR_HTTP_RESPONSE_STATUS_CODE,
8
8
  ATTR_URL_FULL,
9
9
  } from '@opentelemetry/semantic-conventions';
10
- import type { EventBus } from './event-bus/event-bus';
10
+ import { type BusLifecycleEvents, EventBus } from './event-bus/index';
11
11
  import type { ApiClientEvents } from './events';
12
12
  import {
13
13
  getHttpClientRequestDuration,
@@ -30,6 +30,13 @@ export interface APIClientConfig {
30
30
  * returns non-2xx statuses normally, so it emits only on network/timeout.
31
31
  */
32
32
  events?: EventBus<ApiClientEvents>;
33
+ /**
34
+ * Optional lifecycle bus to bridge `api.*` events into the application
35
+ * System Events stream (R5). When `events` is omitted the client
36
+ * constructs an internal `EventBus<ApiClientEvents>` parented to this
37
+ * bus so `api.request.error` appears in the JSONL log.
38
+ */
39
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
33
40
  }
34
41
  /** Per-request overrides: headers, timeout, operation name, and abort signal. */
35
42
  export interface RequestOptions {
@@ -137,9 +144,7 @@ async function readBodyCapped(
137
144
  /**
138
145
  * Typed HTTP client builder wrapping fetch with automatic JSON serialization,
139
146
  * timeout support, and OpenTelemetry tracing on every request.
140
- *
141
147
  * @example
142
- * ```ts
143
148
  * const client = new APIClient({ baseUrl: 'https://api.example.com' });
144
149
  * const data = await client.get<User>('/users/1');
145
150
  * ```
@@ -156,7 +161,9 @@ export class APIClient {
156
161
  this.defaultHeaders = config.defaultHeaders ?? {};
157
162
  this.timeout = config.timeout ?? 30_000;
158
163
  this.fetchFn = config.fetch ?? globalThis.fetch;
159
- this.events = config.events;
164
+ this.events =
165
+ config.events ??
166
+ (config.lifecycleBus ? new EventBus<ApiClientEvents>({ lifecycleBus: config.lifecycleBus }) : undefined);
160
167
  }
161
168
 
162
169
  private buildUrl(path: string): string {
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { attachDefaultObservers, createLifecycleBus } from '../event-bus/default-observers';
14
14
  import { EventBus } from '../event-bus/event-bus';
15
+ import { attachFileObserver } from '../event-bus/file-observer';
15
16
  import type { BusLifecycleEvents, EventMap } from '../event-bus/types';
16
17
  import type { InfraEvents } from '../events';
17
18
  import { getLogger, type Logger } from '../logger';
@@ -65,6 +66,12 @@ async function performShutdown<TAppConfig, TEvents extends EventMap>(
65
66
  * Accepts injected dependencies; never opens files, reads config from disk,
66
67
  * or wires runtime-specific exporters.
67
68
  *
69
+ * The returned `lifecycleBus` is the parent of the application `events` bus and
70
+ * can be passed to RuleEngine, WorkflowService, AiRunner, TeamOrchestrator, and
71
+ * APIClient so their domain events share one System Events stream. A supplied
72
+ * file-observer writer records that stream as JSONL; the Node subpath supplies
73
+ * the writer and default `logs/system-events.jsonl` path.
74
+ *
68
75
  * Startup is plugin-driven. Built-in service plugins are registered in dependency
69
76
  * order, then `loadAll()` + `startAll()` run them forward:
70
77
  * 1. Resolve bootstrap config; build EventBus + PluginHost
@@ -119,10 +126,13 @@ export async function runApplication<TAppConfig = unknown, TEvents extends Event
119
126
  enabled: schedOpts?.enabled ?? false,
120
127
  autoStart: schedOpts?.autoStart ?? true,
121
128
  };
122
-
123
129
  const eventsEnabled = options.config?.events?.enabled ?? true;
124
130
  const eventsLifecycle = options.config?.events?.lifecycle ?? true;
125
131
  const eventsDefaultObservers = options.config?.events?.defaultObservers ?? true;
132
+ const eventsFileObserver = options.config?.events?.fileObserver ?? true;
133
+ const fileObserverWriter = options.services?.fileObserverWriter;
134
+ const eventsFilePath =
135
+ options.config?.events?.filePath ?? (fileObserverWriter !== undefined ? 'logs/system-events.jsonl' : undefined);
126
136
 
127
137
  const state: RuntimeState<TAppConfig, TEvents> = {
128
138
  app: undefined,
@@ -143,6 +153,19 @@ export async function runApplication<TAppConfig = unknown, TEvents extends Event
143
153
  attachDefaultObservers(lifecycleBus);
144
154
  }
145
155
 
156
+ // Attach the JSONL file observer when a writer is available. The
157
+ // portable subpath never opens files (ADR-011): the Node subpath
158
+ // injects `createNodeFileSystem()` (or a test stub) and resolves the
159
+ // default path against the project root. Without a writer or path
160
+ // the observer is a no-op even if `events.fileObserver` is `true`.
161
+ let fileObserverAttached = false;
162
+ let fileObserverPath: string | undefined;
163
+ if (lifecycleBus && eventsFileObserver && eventsFilePath !== undefined && fileObserverWriter !== undefined) {
164
+ attachFileObserver(lifecycleBus, eventsFilePath, fileObserverWriter);
165
+ fileObserverAttached = true;
166
+ fileObserverPath = eventsFilePath;
167
+ }
168
+
146
169
  const events =
147
170
  options.services?.events ??
148
171
  options.config?.events?.bus ??
@@ -173,7 +196,13 @@ export async function runApplication<TAppConfig = unknown, TEvents extends Event
173
196
  // ── Build runtime handle (before startAll so plugins can capture it) ─
174
197
  const resolvedConfig: ApplicationBootstrapConfig = {
175
198
  logging: loggingConfig,
176
- events: { enabled: eventsEnabled, lifecycle: eventsLifecycle, defaultObservers: eventsDefaultObservers },
199
+ events: {
200
+ enabled: eventsEnabled,
201
+ lifecycle: eventsLifecycle,
202
+ defaultObservers: eventsDefaultObservers,
203
+ fileObserver: fileObserverAttached,
204
+ ...(fileObserverPath !== undefined ? { filePath: fileObserverPath } : {}),
205
+ },
177
206
  telemetry: telemetryConfig,
178
207
  scheduler: schedulerConfig,
179
208
  };
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { EventBus } from '../event-bus/event-bus';
12
+ import type { FileObserverWriter } from '../event-bus/file-observer';
12
13
  import type { BusLifecycleEvents, EventMap } from '../event-bus/types';
13
14
  import type { InfraEvents } from '../events';
14
15
  import type { Logger, LogLevel } from '../logger';
@@ -43,6 +44,20 @@ export interface EventsOptions<TEvents extends EventMap = InfraEvents> {
43
44
  lifecycle?: boolean;
44
45
  /** Attach default observers (log + telemetry). Default `true`. */
45
46
  defaultObservers?: boolean;
47
+ /**
48
+ * Attach a file observer that appends JSONL rows for every lifecycle
49
+ * event to `filePath`. Default `true` when a writer is available
50
+ * (Node subpath injects `createNodeFileSystem()`; portable callers must
51
+ * supply one via `services.fileObserverWriter`). No-op when `lifecycle`
52
+ * is disabled or no writer is present.
53
+ */
54
+ fileObserver?: boolean;
55
+ /**
56
+ * JSONL output path. Defaults to `logs/system-events.jsonl` when a writer
57
+ * is supplied; the Node subpath resolves this against the project root.
58
+ * Portable callers may override it with an absolute path.
59
+ */
60
+ filePath?: string;
46
61
  /** Pre-built event bus (skips creation when provided). */
47
62
  bus?: EventBus<TEvents>;
48
63
  }
@@ -81,7 +96,15 @@ export interface ApplicationBootstrapConfig {
81
96
  readonly logging: Readonly<
82
97
  Required<Pick<LoggingOptions, 'enabled' | 'level' | 'console' | 'json'>> & { fileSink?: (line: string) => void }
83
98
  >;
84
- readonly events: { enabled: boolean; lifecycle: boolean; defaultObservers: boolean };
99
+ readonly events: {
100
+ enabled: boolean;
101
+ lifecycle: boolean;
102
+ defaultObservers: boolean;
103
+ /** Whether the JSONL file observer was attached (false when no writer is available). */
104
+ fileObserver: boolean;
105
+ /** Resolved JSONL absolute path, or undefined when fileObserver is false. */
106
+ filePath?: string;
107
+ };
85
108
  readonly telemetry: { enabled: boolean; serviceName: string; environment: string; dbStatementDebug: boolean };
86
109
  readonly scheduler: { enabled: boolean; autoStart: boolean };
87
110
  }
@@ -93,6 +116,13 @@ export interface ApplicationServices<TEvents extends EventMap = InfraEvents> {
93
116
  logger?: Logger;
94
117
  events?: EventBus<TEvents>;
95
118
  lifecycleBus?: EventBus<BusLifecycleEvents>;
119
+ /**
120
+ * Writer for `attachFileObserver`. Portable subpath never opens files
121
+ * (ADR-011); the Node subpath injects `createNodeFileSystem()`. When
122
+ * absent, the file observer is skipped even if `events.fileObserver`
123
+ * is `true`.
124
+ */
125
+ fileObserverWriter?: FileObserverWriter;
96
126
  db?: DbAdapterLike;
97
127
  scheduler?: SchedulerAdapter;
98
128
  /** Pre-built plugin host (when injecting instead of constructing). */
@@ -19,7 +19,7 @@ import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
19
19
  import { dirname } from 'node:path';
20
20
 
21
21
  import { createDbAdapter, type DbAdapter } from '@gobing-ai/ts-db';
22
- import { interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
22
+ import { createNodeFileSystem, interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
23
23
 
24
24
  import { runApplication } from './application/index';
25
25
  import { dbPlugin } from './application/plugins/builtins';
@@ -252,11 +252,13 @@ export async function runNodeApplication<TAppConfig = unknown, TEvents extends E
252
252
  const yamlLog = yamlBootstrap.logging as Partial<LoggingOptions> | undefined;
253
253
  const yamlTel = yamlBootstrap.telemetry as Partial<NodeTelemetryBootstrapOptions> | undefined;
254
254
  const yamlSched = yamlBootstrap.scheduler as Partial<SchedulerOptions> | undefined;
255
+ const yamlEvents = yamlBootstrap.events as Partial<EventsOptions<TEvents>> | undefined;
255
256
  const databaseOpts = (yamlBootstrap.database ?? {}) as Record<string, unknown>;
256
257
 
257
258
  const loggingOpts: Partial<LoggingOptions> = { ...yamlLog, ...options.config?.logging };
258
259
  const telemetryOpts: Partial<NodeTelemetryBootstrapOptions> = { ...yamlTel, ...options.config?.telemetry };
259
260
  const schedulerOpts: Partial<SchedulerOptions> = { ...yamlSched, ...options.config?.scheduler };
261
+ const eventsOpts: EventsOptions<TEvents> = { ...yamlEvents, ...options.config?.events };
260
262
 
261
263
  const logFilePath = (yamlBootstrap.logging as Record<string, unknown> | undefined)?.filePath as string | undefined;
262
264
  const loggingConfig: Partial<LoggingOptions> =
@@ -319,14 +321,41 @@ export async function runNodeApplication<TAppConfig = unknown, TEvents extends E
319
321
  }
320
322
  }
321
323
 
324
+ // ── Inject Node file observer (ADR-011 writer for the JSONL bus log) ──
325
+ // The portable `runApplication` never opens files; the Node subpath
326
+ // supplies the writer and resolves the default path against the project
327
+ // root. Caller-provided `services.fileObserverWriter` and
328
+ // `config.events.filePath` overrides win; `fileObserver: false` disables.
329
+ const injectedFileSystem = createNodeFileSystem();
330
+ const fileObserverWriter = options.services?.fileObserverWriter ?? {
331
+ ensureDir: (dir: string) => {
332
+ mkdirSync(dir, { recursive: true });
333
+ },
334
+ appendFile: (path: string, content: string) => {
335
+ appendFileSync(path, content);
336
+ },
337
+ };
338
+ const eventsFilePath = eventsOpts.filePath ?? injectedFileSystem.resolve('logs', 'system-events.jsonl');
339
+ const eventsConfig: EventsOptions<TEvents> = {
340
+ ...eventsOpts,
341
+ filePath: eventsFilePath,
342
+ ...(eventsOpts.fileObserver === false ? {} : { fileObserver: true }),
343
+ };
344
+
322
345
  // ── Delegate to portable runApplication ─────────────────────────────
323
346
  // Node-specific cleanup is handled by plugins in the service ring —
324
347
  // node-telemetry onStop, owned-db onStop. No manual try/catch or stop
325
348
  // override needed.
326
349
  return await runApplication<TAppConfig, TEvents>({
327
- config: { ...options.config, logging: loggingConfig, telemetry: telemetryOpts, scheduler: schedulerConfig },
350
+ config: {
351
+ ...options.config,
352
+ logging: loggingConfig,
353
+ telemetry: telemetryOpts,
354
+ scheduler: schedulerConfig,
355
+ events: eventsConfig,
356
+ },
328
357
  appConfig: loadedAppConfig,
329
- services: { ...options.services, ...(dbAdapter ? { db: dbAdapter } : {}) },
358
+ services: { ...options.services, fileObserverWriter, ...(dbAdapter ? { db: dbAdapter } : {}) },
330
359
  start: options.start,
331
360
  stop: options.stop,
332
361
  plugins: plugins.length ? plugins : undefined,
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export {
15
15
  attachFileObserver,
16
16
  attachLogObserver,
17
17
  attachTelemetryObserver,
18
+ type BusLifecycleEvents,
18
19
  createLifecycleBus,
19
20
  EventBus,
20
21
  type EventMap,