apcore-js 0.5.0 → 0.7.0

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 (319) hide show
  1. package/README.md +1 -1
  2. package/dist/acl.d.ts +27 -0
  3. package/dist/acl.d.ts.map +1 -0
  4. package/dist/acl.js +175 -0
  5. package/dist/acl.js.map +1 -0
  6. package/dist/approval.d.ts +85 -0
  7. package/dist/approval.d.ts.map +1 -0
  8. package/dist/approval.js +73 -0
  9. package/dist/approval.js.map +1 -0
  10. package/dist/async-task.d.ts +90 -0
  11. package/dist/async-task.d.ts.map +1 -0
  12. package/dist/async-task.js +215 -0
  13. package/dist/async-task.js.map +1 -0
  14. package/dist/bindings.d.ts +12 -0
  15. package/dist/bindings.d.ts.map +1 -0
  16. package/dist/bindings.js +185 -0
  17. package/dist/bindings.js.map +1 -0
  18. package/dist/cancel.d.ts +14 -0
  19. package/dist/cancel.d.ts.map +1 -0
  20. package/dist/cancel.js +27 -0
  21. package/dist/cancel.js.map +1 -0
  22. package/dist/config.d.ts +9 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +23 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/context.d.ts +50 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +87 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/decorator.d.ts +57 -0
  31. package/dist/decorator.d.ts.map +1 -0
  32. package/dist/decorator.js +74 -0
  33. package/dist/decorator.js.map +1 -0
  34. package/dist/errors.d.ts +204 -0
  35. package/dist/errors.d.ts.map +1 -0
  36. package/dist/errors.js +364 -0
  37. package/dist/errors.js.map +1 -0
  38. package/dist/executor.d.ts +82 -0
  39. package/dist/executor.d.ts.map +1 -0
  40. package/dist/executor.js +489 -0
  41. package/dist/executor.js.map +1 -0
  42. package/dist/extensions.d.ts +58 -0
  43. package/dist/extensions.d.ts.map +1 -0
  44. package/dist/extensions.js +239 -0
  45. package/dist/extensions.js.map +1 -0
  46. package/{src/index.ts → dist/index.d.ts} +6 -63
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +45 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/middleware/adapters.d.ts +18 -0
  51. package/dist/middleware/adapters.d.ts.map +1 -0
  52. package/dist/middleware/adapters.js +25 -0
  53. package/dist/middleware/adapters.js.map +1 -0
  54. package/dist/middleware/base.d.ts +10 -0
  55. package/dist/middleware/base.d.ts.map +1 -0
  56. package/dist/middleware/base.js +15 -0
  57. package/dist/middleware/base.js.map +1 -0
  58. package/{src/middleware/index.ts → dist/middleware/index.d.ts} +1 -0
  59. package/dist/middleware/index.d.ts.map +1 -0
  60. package/dist/middleware/index.js +5 -0
  61. package/dist/middleware/index.js.map +1 -0
  62. package/dist/middleware/logging.d.ts +25 -0
  63. package/dist/middleware/logging.d.ts.map +1 -0
  64. package/dist/middleware/logging.js +64 -0
  65. package/dist/middleware/logging.js.map +1 -0
  66. package/dist/middleware/manager.d.ts +21 -0
  67. package/dist/middleware/manager.d.ts.map +1 -0
  68. package/dist/middleware/manager.js +77 -0
  69. package/dist/middleware/manager.js.map +1 -0
  70. package/dist/module.d.ts +31 -0
  71. package/dist/module.d.ts.map +1 -0
  72. package/dist/module.js +12 -0
  73. package/dist/module.js.map +1 -0
  74. package/dist/observability/context-logger.d.ts +54 -0
  75. package/dist/observability/context-logger.d.ts.map +1 -0
  76. package/dist/observability/context-logger.js +151 -0
  77. package/dist/observability/context-logger.js.map +1 -0
  78. package/{src/observability/index.ts → dist/observability/index.d.ts} +1 -0
  79. package/dist/observability/index.d.ts.map +1 -0
  80. package/dist/observability/index.js +4 -0
  81. package/dist/observability/index.js.map +1 -0
  82. package/dist/observability/metrics.d.ts +30 -0
  83. package/dist/observability/metrics.d.ts.map +1 -0
  84. package/dist/observability/metrics.js +177 -0
  85. package/dist/observability/metrics.js.map +1 -0
  86. package/dist/observability/tracing.d.ts +62 -0
  87. package/dist/observability/tracing.d.ts.map +1 -0
  88. package/dist/observability/tracing.js +184 -0
  89. package/dist/observability/tracing.js.map +1 -0
  90. package/dist/registry/dependencies.d.ts +6 -0
  91. package/dist/registry/dependencies.d.ts.map +1 -0
  92. package/dist/registry/dependencies.js +83 -0
  93. package/dist/registry/dependencies.js.map +1 -0
  94. package/dist/registry/entry-point.d.ts +6 -0
  95. package/dist/registry/entry-point.d.ts.map +1 -0
  96. package/dist/registry/entry-point.js +55 -0
  97. package/dist/registry/entry-point.js.map +1 -0
  98. package/{src/registry/index.ts → dist/registry/index.d.ts} +1 -0
  99. package/dist/registry/index.d.ts.map +1 -0
  100. package/dist/registry/index.js +8 -0
  101. package/dist/registry/index.js.map +1 -0
  102. package/dist/registry/metadata.d.ts +9 -0
  103. package/dist/registry/metadata.d.ts.map +1 -0
  104. package/dist/registry/metadata.js +105 -0
  105. package/dist/registry/metadata.js.map +1 -0
  106. package/dist/registry/registry.d.ts +102 -0
  107. package/dist/registry/registry.d.ts.map +1 -0
  108. package/dist/registry/registry.js +534 -0
  109. package/dist/registry/registry.js.map +1 -0
  110. package/dist/registry/scanner.d.ts +7 -0
  111. package/dist/registry/scanner.d.ts.map +1 -0
  112. package/dist/registry/scanner.js +164 -0
  113. package/dist/registry/scanner.js.map +1 -0
  114. package/dist/registry/schema-export.d.ts +9 -0
  115. package/dist/registry/schema-export.d.ts.map +1 -0
  116. package/dist/registry/schema-export.js +132 -0
  117. package/dist/registry/schema-export.js.map +1 -0
  118. package/dist/registry/types.d.ts +29 -0
  119. package/dist/registry/types.d.ts.map +1 -0
  120. package/dist/registry/types.js +5 -0
  121. package/dist/registry/types.js.map +1 -0
  122. package/dist/registry/validation.d.ts +9 -0
  123. package/dist/registry/validation.d.ts.map +1 -0
  124. package/dist/registry/validation.js +33 -0
  125. package/dist/registry/validation.js.map +1 -0
  126. package/dist/schema/annotations.d.ts +8 -0
  127. package/dist/schema/annotations.d.ts.map +1 -0
  128. package/dist/schema/annotations.js +52 -0
  129. package/dist/schema/annotations.js.map +1 -0
  130. package/dist/schema/exporter.d.ts +13 -0
  131. package/dist/schema/exporter.d.ts.map +1 -0
  132. package/dist/schema/exporter.js +71 -0
  133. package/dist/schema/exporter.js.map +1 -0
  134. package/dist/schema/index.d.ts +9 -0
  135. package/dist/schema/index.d.ts.map +1 -0
  136. package/{src/schema/index.ts → dist/schema/index.js} +1 -7
  137. package/dist/schema/index.js.map +1 -0
  138. package/dist/schema/loader.d.ts +30 -0
  139. package/dist/schema/loader.d.ts.map +1 -0
  140. package/dist/schema/loader.js +260 -0
  141. package/dist/schema/loader.js.map +1 -0
  142. package/dist/schema/ref-resolver.d.ts +19 -0
  143. package/dist/schema/ref-resolver.d.ts.map +1 -0
  144. package/dist/schema/ref-resolver.js +212 -0
  145. package/dist/schema/ref-resolver.js.map +1 -0
  146. package/dist/schema/strict.d.ts +7 -0
  147. package/dist/schema/strict.d.ts.map +1 -0
  148. package/dist/schema/strict.js +127 -0
  149. package/dist/schema/strict.js.map +1 -0
  150. package/dist/schema/types.d.ts +53 -0
  151. package/dist/schema/types.d.ts.map +1 -0
  152. package/dist/schema/types.js +31 -0
  153. package/dist/schema/types.js.map +1 -0
  154. package/dist/schema/validator.d.ts +16 -0
  155. package/dist/schema/validator.d.ts.map +1 -0
  156. package/dist/schema/validator.js +71 -0
  157. package/dist/schema/validator.js.map +1 -0
  158. package/dist/trace-context.d.ts +35 -0
  159. package/dist/trace-context.d.ts.map +1 -0
  160. package/dist/trace-context.js +86 -0
  161. package/dist/trace-context.js.map +1 -0
  162. package/dist/utils/index.d.ts +11 -0
  163. package/dist/utils/index.d.ts.map +1 -0
  164. package/dist/utils/index.js +32 -0
  165. package/dist/utils/index.js.map +1 -0
  166. package/dist/utils/pattern.d.ts +5 -0
  167. package/dist/utils/pattern.d.ts.map +1 -0
  168. package/dist/utils/pattern.js +31 -0
  169. package/dist/utils/pattern.js.map +1 -0
  170. package/package.json +24 -3
  171. package/.claude/settings.local.json +0 -12
  172. package/.github/workflows/ci.yml +0 -39
  173. package/.gitmessage +0 -60
  174. package/.pre-commit-config.yaml +0 -28
  175. package/CHANGELOG.md +0 -214
  176. package/CLAUDE.md +0 -68
  177. package/apcore-logo.svg +0 -79
  178. package/planning/acl-system/overview.md +0 -54
  179. package/planning/acl-system/plan.md +0 -92
  180. package/planning/acl-system/state.json +0 -76
  181. package/planning/acl-system/tasks/acl-core.md +0 -226
  182. package/planning/acl-system/tasks/acl-rule.md +0 -92
  183. package/planning/acl-system/tasks/conditional-rules.md +0 -259
  184. package/planning/acl-system/tasks/pattern-matching.md +0 -152
  185. package/planning/acl-system/tasks/yaml-loading.md +0 -271
  186. package/planning/core-executor/overview.md +0 -53
  187. package/planning/core-executor/plan.md +0 -88
  188. package/planning/core-executor/state.json +0 -76
  189. package/planning/core-executor/tasks/async-support.md +0 -106
  190. package/planning/core-executor/tasks/execution-pipeline.md +0 -113
  191. package/planning/core-executor/tasks/redaction.md +0 -85
  192. package/planning/core-executor/tasks/safety-checks.md +0 -65
  193. package/planning/core-executor/tasks/setup.md +0 -75
  194. package/planning/decorator-bindings/overview.md +0 -62
  195. package/planning/decorator-bindings/plan.md +0 -104
  196. package/planning/decorator-bindings/state.json +0 -87
  197. package/planning/decorator-bindings/tasks/binding-directory.md +0 -79
  198. package/planning/decorator-bindings/tasks/binding-loader.md +0 -148
  199. package/planning/decorator-bindings/tasks/explicit-schemas.md +0 -85
  200. package/planning/decorator-bindings/tasks/function-module.md +0 -127
  201. package/planning/decorator-bindings/tasks/module-factory.md +0 -89
  202. package/planning/decorator-bindings/tasks/schema-modes.md +0 -142
  203. package/planning/middleware-system/overview.md +0 -48
  204. package/planning/middleware-system/plan.md +0 -102
  205. package/planning/middleware-system/state.json +0 -65
  206. package/planning/middleware-system/tasks/adapters.md +0 -170
  207. package/planning/middleware-system/tasks/base.md +0 -115
  208. package/planning/middleware-system/tasks/logging-middleware.md +0 -304
  209. package/planning/middleware-system/tasks/manager.md +0 -313
  210. package/planning/observability/overview.md +0 -53
  211. package/planning/observability/plan.md +0 -119
  212. package/planning/observability/state.json +0 -98
  213. package/planning/observability/tasks/context-logger.md +0 -201
  214. package/planning/observability/tasks/exporters.md +0 -121
  215. package/planning/observability/tasks/metrics-collector.md +0 -162
  216. package/planning/observability/tasks/metrics-middleware.md +0 -141
  217. package/planning/observability/tasks/obs-logging-middleware.md +0 -179
  218. package/planning/observability/tasks/span-model.md +0 -120
  219. package/planning/observability/tasks/tracing-middleware.md +0 -179
  220. package/planning/overview.md +0 -81
  221. package/planning/registry-system/overview.md +0 -57
  222. package/planning/registry-system/plan.md +0 -114
  223. package/planning/registry-system/state.json +0 -109
  224. package/planning/registry-system/tasks/dependencies.md +0 -157
  225. package/planning/registry-system/tasks/entry-point.md +0 -148
  226. package/planning/registry-system/tasks/metadata.md +0 -198
  227. package/planning/registry-system/tasks/registry-core.md +0 -323
  228. package/planning/registry-system/tasks/scanner.md +0 -172
  229. package/planning/registry-system/tasks/schema-export.md +0 -261
  230. package/planning/registry-system/tasks/types.md +0 -124
  231. package/planning/registry-system/tasks/validation.md +0 -177
  232. package/planning/schema-system/overview.md +0 -56
  233. package/planning/schema-system/plan.md +0 -121
  234. package/planning/schema-system/state.json +0 -98
  235. package/planning/schema-system/tasks/exporter.md +0 -153
  236. package/planning/schema-system/tasks/loader.md +0 -106
  237. package/planning/schema-system/tasks/ref-resolver.md +0 -133
  238. package/planning/schema-system/tasks/strict-mode.md +0 -140
  239. package/planning/schema-system/tasks/typebox-generation.md +0 -133
  240. package/planning/schema-system/tasks/types-and-annotations.md +0 -160
  241. package/planning/schema-system/tasks/validator.md +0 -149
  242. package/src/acl.ts +0 -200
  243. package/src/async-task.ts +0 -267
  244. package/src/bindings.ts +0 -207
  245. package/src/cancel.ts +0 -32
  246. package/src/config.ts +0 -24
  247. package/src/context.ts +0 -160
  248. package/src/decorator.ts +0 -110
  249. package/src/errors.ts +0 -429
  250. package/src/executor.ts +0 -493
  251. package/src/extensions.ts +0 -265
  252. package/src/middleware/adapters.ts +0 -54
  253. package/src/middleware/base.ts +0 -33
  254. package/src/middleware/logging.ts +0 -103
  255. package/src/middleware/manager.ts +0 -105
  256. package/src/module.ts +0 -43
  257. package/src/observability/context-logger.ts +0 -203
  258. package/src/observability/metrics.ts +0 -214
  259. package/src/observability/tracing.ts +0 -252
  260. package/src/registry/dependencies.ts +0 -99
  261. package/src/registry/entry-point.ts +0 -64
  262. package/src/registry/metadata.ts +0 -111
  263. package/src/registry/registry.ts +0 -580
  264. package/src/registry/scanner.ts +0 -168
  265. package/src/registry/schema-export.ts +0 -181
  266. package/src/registry/types.ts +0 -32
  267. package/src/registry/validation.ts +0 -38
  268. package/src/schema/annotations.ts +0 -68
  269. package/src/schema/exporter.ts +0 -90
  270. package/src/schema/loader.ts +0 -273
  271. package/src/schema/ref-resolver.ts +0 -244
  272. package/src/schema/strict.ts +0 -136
  273. package/src/schema/types.ts +0 -73
  274. package/src/schema/validator.ts +0 -82
  275. package/src/trace-context.ts +0 -102
  276. package/src/utils/index.ts +0 -5
  277. package/src/utils/pattern.ts +0 -30
  278. package/tests/async-task.test.ts +0 -335
  279. package/tests/helpers.ts +0 -30
  280. package/tests/integration/test-acl-safety.test.ts +0 -269
  281. package/tests/integration/test-binding-executor.test.ts +0 -194
  282. package/tests/integration/test-e2e-flow.test.ts +0 -117
  283. package/tests/integration/test-error-propagation.test.ts +0 -259
  284. package/tests/integration/test-middleware-chain.test.ts +0 -120
  285. package/tests/integration/test-observability-integration.test.ts +0 -438
  286. package/tests/observability/test-context-logger.test.ts +0 -123
  287. package/tests/observability/test-metrics.test.ts +0 -186
  288. package/tests/observability/test-tracing.test.ts +0 -303
  289. package/tests/registry/test-dependencies.test.ts +0 -70
  290. package/tests/registry/test-entry-point.test.ts +0 -133
  291. package/tests/registry/test-metadata.test.ts +0 -265
  292. package/tests/registry/test-registry.test.ts +0 -1397
  293. package/tests/registry/test-scanner.test.ts +0 -257
  294. package/tests/registry/test-schema-export.test.ts +0 -355
  295. package/tests/registry/test-validation.test.ts +0 -75
  296. package/tests/schema/test-annotations.test.ts +0 -137
  297. package/tests/schema/test-exporter.test.ts +0 -172
  298. package/tests/schema/test-loader.test.ts +0 -461
  299. package/tests/schema/test-ref-resolver.test.ts +0 -530
  300. package/tests/schema/test-strict.test.ts +0 -348
  301. package/tests/schema/test-validator.test.ts +0 -64
  302. package/tests/test-acl.test.ts +0 -423
  303. package/tests/test-bindings.test.ts +0 -227
  304. package/tests/test-cancel.test.ts +0 -71
  305. package/tests/test-config.test.ts +0 -76
  306. package/tests/test-context.test.ts +0 -266
  307. package/tests/test-decorator.test.ts +0 -173
  308. package/tests/test-errors.test.ts +0 -647
  309. package/tests/test-executor-stream.test.ts +0 -208
  310. package/tests/test-executor.test.ts +0 -252
  311. package/tests/test-extensions.test.ts +0 -310
  312. package/tests/test-logging-middleware.test.ts +0 -150
  313. package/tests/test-middleware-manager.test.ts +0 -185
  314. package/tests/test-middleware.test.ts +0 -86
  315. package/tests/test-trace-context.test.ts +0 -251
  316. package/tests/utils/test-pattern.test.ts +0 -109
  317. package/tsconfig.build.json +0 -8
  318. package/tsconfig.json +0 -20
  319. package/vitest.config.ts +0 -18
@@ -1,119 +0,0 @@
1
- # Implementation Plan: Observability
2
-
3
- ## Goal
4
-
5
- Implement the three pillars of observability -- tracing, metrics, and logging -- as middleware-integrated components that provide runtime visibility into the apcore execution pipeline. Each pillar operates through a `Middleware` subclass that uses stack-based state in the shared `context.data` record to handle nested module-to-module calls correctly.
6
-
7
- ## Architecture Design
8
-
9
- ### Component Structure
10
-
11
- The observability module is organized into three source files, each housing one pillar and its associated middleware:
12
-
13
- - **Tracing** (`observability/tracing.ts`, ~190 lines) -- `Span` interface describing a trace span with `traceId`, `spanId`, `parentSpanId`, timing, status, attributes, and events. `createSpan()` factory function generates spans with `randomBytes(8).toString('hex')` for span IDs. `SpanExporter` interface with two implementations: `StdoutExporter` (JSON.stringify to console.log) and `InMemoryExporter` (bounded array, shift()-based eviction, configurable max 10,000). `TracingMiddleware` extends `Middleware` with stack-based span management and 4 sampling strategies.
14
-
15
- - **Metrics** (`observability/metrics.ts`, ~210 lines) -- `MetricsCollector` class with counter (`increment`) and histogram (`observe`) primitives. Uses string-encoded composite keys in the format `name|key1=val1,key2=val2` stored in `Map<string, number>`. No thread locking required (Node.js single-threaded). Histogram support with configurable buckets (13 Prometheus-standard defaults), `+Inf` bucket, and full Prometheus text format export. Convenience methods `incrementCalls()`, `incrementErrors()`, `observeDuration()` for apcore-standard metrics. `MetricsMiddleware` extends `Middleware` with stack-based `performance.now()` timing.
16
-
17
- - **Logging** (`observability/context-logger.ts`, ~200 lines) -- `ContextLogger` class with 6 log levels (trace/debug/info/warn/error/fatal mapped to numeric values 0-50), JSON and text output formats, automatic `_secret_`-prefixed key redaction, and `fromContext()` static factory that binds trace ID, module ID, and caller ID. Output targets a `WritableOutput` interface (defaults to `process.stderr`). `ObsLoggingMiddleware` extends `Middleware` with stack-based timing and configurable input/output logging.
18
-
19
- ### Three Pillars Architecture
20
-
21
- ```
22
- Executor Pipeline
23
- |
24
- +--------------+--------------+
25
- | | |
26
- TracingMiddleware MetricsMiddleware ObsLoggingMiddleware
27
- | | |
28
- SpanExporter MetricsCollector ContextLogger
29
- / \ | |
30
- StdoutExp InMemoryExp Prometheus JSON/Text
31
- Export Output
32
- ```
33
-
34
- ### Data Flow
35
-
36
- Each middleware hooks into the executor pipeline at three points: `before()`, `after()`, and `onError()`. State is carried across nested calls via the shared `context.data` record using reserved keys:
37
-
38
- 1. **TracingMiddleware** stores a span stack at `context.data['_tracing_spans']` and a sampling decision at `context.data['_tracing_sampled']`. On `before()`, a new span is created and pushed onto the stack with the current top-of-stack span as parent. On `after()`/`onError()`, the span is popped, finalized with end time and status, and conditionally exported based on the sampling decision.
39
-
40
- 2. **MetricsMiddleware** stores a timing stack at `context.data['_metrics_starts']`. On `before()`, `performance.now()` is pushed. On `after()`/`onError()`, the start time is popped and duration computed as `(performance.now() - startTime) / 1000` (converted to seconds for Prometheus conventions).
41
-
42
- 3. **ObsLoggingMiddleware** stores a timing stack at `context.data['_obs_logging_starts']`. On `before()`, `performance.now()` is pushed and a "Module call started" log entry is emitted. On `after()`, the start time is popped, duration computed in milliseconds, and "Module call completed" is emitted. On `onError()`, "Module call failed" is emitted with error details.
43
-
44
- ### Technical Choices and Rationale
45
-
46
- - **`performance.now()` for timing**: Provides monotonic millisecond resolution. Unlike `Date.now()`, it is not affected by system clock adjustments. Unlike Python's `time.time()` (wall-clock seconds), this returns monotonic milliseconds, so MetricsMiddleware divides by 1000 for Prometheus-standard seconds.
47
-
48
- - **String-encoded composite keys**: `MetricsCollector` stores counters and histogram data using keys in the format `name|key1=val1,key2=val2`. This avoids the overhead of nested Maps or object hashing and allows simple Map lookups. Labels are sorted alphabetically for deterministic key generation.
49
-
50
- - **No thread locking**: Node.js runs JavaScript on a single thread. Unlike the Python implementation which requires threading locks for metric state, no synchronization primitives are needed here.
51
-
52
- - **`randomBytes(8).toString('hex')` for span IDs**: Produces 16-character hex strings (64 bits of entropy). Uses Node.js `node:crypto` for cryptographic randomness rather than `crypto.randomUUID().slice(0,16)` to avoid partial UUID collisions.
53
-
54
- - **Bounded array with `shift()` for InMemoryExporter**: Simple FIFO eviction. Python uses `collections.deque(maxlen=...)` which is O(1) for left-pop; JavaScript `Array.shift()` is O(n) but acceptable for the expected span volumes. A ring buffer could be a future optimization.
55
-
56
- - **No OTel bridge**: An `OTLPExporter` is not yet implemented. The `SpanExporter` interface is designed to be compatible with future OpenTelemetry integration, but no OTel SDK dependency exists today.
57
-
58
- - **`WritableOutput` interface for logger**: Abstracts the output target to support both `process.stderr` (default) and test buffers. Avoids coupling to Node.js streams directly.
59
-
60
- ## Task Breakdown
61
-
62
- ```mermaid
63
- graph TD
64
- T1[span-model] --> T2[exporters]
65
- T1 --> T3[tracing-middleware]
66
- T2 --> T3
67
- T4[metrics-collector] --> T5[metrics-middleware]
68
- T6[context-logger] --> T7[obs-logging-middleware]
69
- ```
70
-
71
- | Task ID | Title | Estimated Time | Dependencies |
72
- |---------|-------|---------------|--------------|
73
- | span-model | Span interface and createSpan() factory | 1h | none |
74
- | exporters | StdoutExporter and InMemoryExporter | 1.5h | span-model |
75
- | tracing-middleware | TracingMiddleware with sampling strategies | 3h | span-model, exporters |
76
- | metrics-collector | MetricsCollector with Prometheus export | 3h | none |
77
- | metrics-middleware | MetricsMiddleware with stack-based timing | 2h | metrics-collector |
78
- | context-logger | ContextLogger with formats and redaction | 2h | none |
79
- | obs-logging-middleware | ObsLoggingMiddleware with structured logging | 2h | context-logger |
80
-
81
- ## Risks and Considerations
82
-
83
- - **`Array.shift()` performance in InMemoryExporter**: Eviction via `shift()` is O(n) per call when the buffer is full. For high-throughput tracing with maxSpans in the thousands, this is acceptable. If maxSpans grows to tens of thousands, consider a ring buffer implementation.
84
- - **Missing OTLPExporter**: Production deployments expecting OpenTelemetry Protocol export will need a custom `SpanExporter` implementation or a future `OTLPExporter` addition. The `SpanExporter` interface is stable and ready for this extension.
85
- - **Prometheus export is in-memory only**: `MetricsCollector.exportPrometheus()` generates text on demand. There is no HTTP endpoint or push gateway integration. Consumers must call `exportPrometheus()` and serve or ship the output themselves.
86
- - **Sampling decision propagation**: The `_tracing_sampled` flag is set once per context (on first `before()` call) and inherited by all nested spans. This means proportional sampling is all-or-nothing per trace, not per span. The `error_first` strategy overrides this by always exporting error spans regardless of the sampling decision.
87
- - **Logger output blocking**: `process.stderr.write()` is synchronous for small payloads on most platforms. High-volume logging could introduce backpressure in latency-sensitive paths.
88
-
89
- ## Acceptance Criteria
90
-
91
- - [x] `Span` interface defines all required fields (traceId, spanId, parentSpanId, name, startTime, endTime, status, attributes, events)
92
- - [x] `createSpan()` generates unique 16-hex-char span IDs via `randomBytes(8).toString('hex')`
93
- - [x] `StdoutExporter` serializes spans via `JSON.stringify` to `console.log`
94
- - [x] `InMemoryExporter` respects `maxSpans` limit with FIFO eviction via `shift()`
95
- - [x] `InMemoryExporter.getSpans()` returns a defensive copy (spread into new array)
96
- - [x] `TracingMiddleware` supports all 4 sampling strategies: full, proportional, error_first, off
97
- - [x] `TracingMiddleware` validates sampling rate (0.0-1.0) and strategy on construction
98
- - [x] Nested spans correctly chain via `parentSpanId` using stack-based management
99
- - [x] `error_first` strategy always exports error spans even when not sampled
100
- - [x] `MetricsCollector` supports counter increment and histogram observe operations
101
- - [x] String-encoded keys use sorted labels for deterministic composite key generation
102
- - [x] `exportPrometheus()` produces valid Prometheus text format with HELP, TYPE, buckets, sum, count
103
- - [x] `MetricsMiddleware` records calls, errors, and duration via stack-based timing
104
- - [x] Duration is converted from milliseconds to seconds for Prometheus conventions
105
- - [x] `ContextLogger` supports JSON and text output formats with level filtering
106
- - [x] `_secret_`-prefixed keys are automatically redacted to `***REDACTED***`
107
- - [x] `fromContext()` binds traceId, moduleId, and callerId from a `Context` instance
108
- - [x] `ObsLoggingMiddleware` emits structured start/complete/error log entries with timing
109
- - [x] All tests pass with `vitest`; zero errors from `tsc --noEmit`
110
-
111
- ## References
112
-
113
- - `src/observability/tracing.ts` -- Span model, exporters, and TracingMiddleware
114
- - `src/observability/metrics.ts` -- MetricsCollector and MetricsMiddleware
115
- - `src/observability/context-logger.ts` -- ContextLogger and ObsLoggingMiddleware
116
- - `src/observability/index.ts` -- Public API barrel exports
117
- - `tests/observability/test-tracing.test.ts` -- Tracing tests
118
- - `tests/observability/test-metrics.test.ts` -- Metrics tests
119
- - `tests/observability/test-context-logger.test.ts` -- Logger tests
@@ -1,98 +0,0 @@
1
- {
2
- "feature": "observability",
3
- "created": "2026-02-16T00:00:00Z",
4
- "updated": "2026-02-16T00:00:00Z",
5
- "status": "completed",
6
- "execution_order": [
7
- "span-model",
8
- "exporters",
9
- "tracing-middleware",
10
- "metrics-collector",
11
- "metrics-middleware",
12
- "context-logger",
13
- "obs-logging-middleware"
14
- ],
15
- "progress": {
16
- "total_tasks": 7,
17
- "completed": 7,
18
- "in_progress": 0,
19
- "pending": 0
20
- },
21
- "tasks": [
22
- {
23
- "id": "span-model",
24
- "file": "tasks/span-model.md",
25
- "title": "Span Interface and createSpan() Factory",
26
- "status": "completed",
27
- "started_at": "2026-02-16T08:00:00Z",
28
- "completed_at": "2026-02-16T09:00:00Z",
29
- "assignee": null,
30
- "commits": []
31
- },
32
- {
33
- "id": "exporters",
34
- "file": "tasks/exporters.md",
35
- "title": "StdoutExporter and InMemoryExporter",
36
- "status": "completed",
37
- "started_at": "2026-02-16T09:00:00Z",
38
- "completed_at": "2026-02-16T10:30:00Z",
39
- "assignee": null,
40
- "commits": []
41
- },
42
- {
43
- "id": "tracing-middleware",
44
- "file": "tasks/tracing-middleware.md",
45
- "title": "TracingMiddleware with Sampling Strategies",
46
- "status": "completed",
47
- "started_at": "2026-02-16T10:30:00Z",
48
- "completed_at": "2026-02-16T13:30:00Z",
49
- "assignee": null,
50
- "commits": []
51
- },
52
- {
53
- "id": "metrics-collector",
54
- "file": "tasks/metrics-collector.md",
55
- "title": "MetricsCollector with Counters, Histograms, Prometheus Export",
56
- "status": "completed",
57
- "started_at": "2026-02-16T08:00:00Z",
58
- "completed_at": "2026-02-16T11:00:00Z",
59
- "assignee": null,
60
- "commits": []
61
- },
62
- {
63
- "id": "metrics-middleware",
64
- "file": "tasks/metrics-middleware.md",
65
- "title": "MetricsMiddleware with Stack-Based Timing",
66
- "status": "completed",
67
- "started_at": "2026-02-16T11:00:00Z",
68
- "completed_at": "2026-02-16T13:00:00Z",
69
- "assignee": null,
70
- "commits": []
71
- },
72
- {
73
- "id": "context-logger",
74
- "file": "tasks/context-logger.md",
75
- "title": "ContextLogger with JSON/Text Formats and Redaction",
76
- "status": "completed",
77
- "started_at": "2026-02-16T08:00:00Z",
78
- "completed_at": "2026-02-16T10:00:00Z",
79
- "assignee": null,
80
- "commits": []
81
- },
82
- {
83
- "id": "obs-logging-middleware",
84
- "file": "tasks/obs-logging-middleware.md",
85
- "title": "ObsLoggingMiddleware with Structured Logging",
86
- "status": "completed",
87
- "started_at": "2026-02-16T10:00:00Z",
88
- "completed_at": "2026-02-16T12:00:00Z",
89
- "assignee": null,
90
- "commits": []
91
- }
92
- ],
93
- "metadata": {
94
- "source_doc": "planning/features/observability.md",
95
- "created_by": "code-forge",
96
- "version": "1.0"
97
- }
98
- }
@@ -1,201 +0,0 @@
1
- # Task: ContextLogger with JSON/Text Formats and Redaction
2
-
3
- ## Goal
4
-
5
- Implement `ContextLogger` that provides structured logging with JSON and text output formats, numeric level filtering (trace/debug/info/warn/error/fatal), automatic redaction of `_secret_`-prefixed keys, and a `fromContext()` static factory method that binds trace, module, and caller metadata from a `Context` instance.
6
-
7
- ## Files Involved
8
-
9
- - `src/observability/context-logger.ts` -- ContextLogger class, LEVELS map, WritableOutput interface
10
- - `src/context.ts` -- Context class (dependency for `fromContext()`)
11
- - `tests/observability/test-context-logger.test.ts` -- Unit tests for ContextLogger
12
-
13
- ## Steps (TDD)
14
-
15
- ### 1. Write failing tests for basic logging
16
-
17
- ```typescript
18
- function createBufferOutput() {
19
- const lines: string[] = [];
20
- return {
21
- output: { write: (s: string) => lines.push(s) },
22
- lines,
23
- };
24
- }
25
-
26
- describe('ContextLogger', () => {
27
- it('logs JSON format by default', () => {
28
- const { output, lines } = createBufferOutput();
29
- const logger = new ContextLogger({ output });
30
- logger.info('test message');
31
- expect(lines).toHaveLength(1);
32
- const parsed = JSON.parse(lines[0]);
33
- expect(parsed.level).toBe('info');
34
- expect(parsed.message).toBe('test message');
35
- expect(parsed.timestamp).toBeDefined();
36
- expect(parsed.logger).toBe('apcore');
37
- });
38
-
39
- it('logs text format', () => {
40
- const { output, lines } = createBufferOutput();
41
- const logger = new ContextLogger({ format: 'text', output });
42
- logger.info('test message');
43
- expect(lines[0]).toContain('[INFO]');
44
- expect(lines[0]).toContain('test message');
45
- });
46
- });
47
- ```
48
-
49
- ### 2. Define WritableOutput interface and level map
50
-
51
- ```typescript
52
- interface WritableOutput {
53
- write(s: string): void;
54
- }
55
-
56
- const LEVELS: Record<string, number> = {
57
- trace: 0,
58
- debug: 10,
59
- info: 20,
60
- warn: 30,
61
- error: 40,
62
- fatal: 50,
63
- };
64
- ```
65
-
66
- ### 3. Implement ContextLogger constructor with options
67
-
68
- ```typescript
69
- export class ContextLogger {
70
- constructor(options?: {
71
- name?: string; // default: 'apcore'
72
- format?: string; // 'json' (default) or 'text'
73
- level?: string; // minimum level, default: 'info'
74
- redactSensitive?: boolean; // default: true
75
- output?: WritableOutput; // default: process.stderr
76
- }) { /* ... */ }
77
- }
78
- ```
79
-
80
- ### 4. Write failing tests for level filtering
81
-
82
- ```typescript
83
- it('respects log level filtering', () => {
84
- const { output, lines } = createBufferOutput();
85
- const logger = new ContextLogger({ level: 'warn', output });
86
- logger.debug('should not appear');
87
- logger.info('should not appear');
88
- logger.warn('should appear');
89
- logger.error('should appear');
90
- expect(lines).toHaveLength(2);
91
- });
92
- ```
93
-
94
- ### 5. Implement _emit() with level check and dual format output
95
-
96
- The internal `_emit()` method:
97
- 1. Checks if the message level meets the minimum threshold
98
- 2. Redacts `_secret_`-prefixed keys in the `extra` record if `redactSensitive` is true
99
- 3. Builds a log entry record with `timestamp`, `level`, `message`, `trace_id`, `module_id`, `caller_id`, `logger`, `extra`
100
- 4. Serializes as JSON (`JSON.stringify + newline`) or text (`timestamp [LEVEL] [trace=...] [module=...] message extras`)
101
-
102
- ### 6. Write failing tests for redaction
103
-
104
- ```typescript
105
- it('redacts _secret_ prefix keys', () => {
106
- const { output, lines } = createBufferOutput();
107
- const logger = new ContextLogger({ output });
108
- logger.info('test', { _secret_token: 'abc123', name: 'Bob' });
109
- const parsed = JSON.parse(lines[0]);
110
- expect(parsed.extra._secret_token).toBe('***REDACTED***');
111
- expect(parsed.extra.name).toBe('Bob');
112
- });
113
-
114
- it('does not redact when disabled', () => {
115
- const { output, lines } = createBufferOutput();
116
- const logger = new ContextLogger({ output, redactSensitive: false });
117
- logger.info('test', { _secret_token: 'abc123' });
118
- const parsed = JSON.parse(lines[0]);
119
- expect(parsed.extra._secret_token).toBe('abc123');
120
- });
121
- ```
122
-
123
- ### 7. Implement _secret_ redaction in _emit()
124
-
125
- ```typescript
126
- if (extra != null && this._redactSensitive) {
127
- redactedExtra = {};
128
- for (const [k, v] of Object.entries(extra)) {
129
- redactedExtra[k] = k.startsWith('_secret_') ? '***REDACTED***' : v;
130
- }
131
- }
132
- ```
133
-
134
- ### 8. Write failing tests for fromContext()
135
-
136
- ```typescript
137
- it('fromContext sets trace/module/caller', () => {
138
- const { output, lines } = createBufferOutput();
139
- const ctx = Context.create(undefined, createIdentity('user1'));
140
- const childCtx = ctx.child('mod.test');
141
- const logger = ContextLogger.fromContext(childCtx, 'test-logger', { output });
142
- logger.info('context log');
143
- const parsed = JSON.parse(lines[0]);
144
- expect(parsed.trace_id).toBe(ctx.traceId);
145
- expect(parsed.module_id).toBe('mod.test');
146
- expect(parsed.logger).toBe('test-logger');
147
- });
148
- ```
149
-
150
- ### 9. Implement fromContext() static factory
151
-
152
- ```typescript
153
- static fromContext(context: Context, name: string, options?: {
154
- format?: string;
155
- level?: string;
156
- redactSensitive?: boolean;
157
- output?: WritableOutput;
158
- }): ContextLogger {
159
- const logger = new ContextLogger({ name, ...options });
160
- logger._traceId = context.traceId;
161
- logger._moduleId = context.callChain.length > 0
162
- ? context.callChain[context.callChain.length - 1]
163
- : null;
164
- logger._callerId = context.callerId;
165
- return logger;
166
- }
167
- ```
168
-
169
- ### 10. Implement all 6 log level methods
170
-
171
- ```typescript
172
- trace(message: string, extra?: Record<string, unknown>): void { this._emit('trace', message, extra); }
173
- debug(message: string, extra?: Record<string, unknown>): void { this._emit('debug', message, extra); }
174
- info(message: string, extra?: Record<string, unknown>): void { this._emit('info', message, extra); }
175
- warn(message: string, extra?: Record<string, unknown>): void { this._emit('warn', message, extra); }
176
- error(message: string, extra?: Record<string, unknown>): void { this._emit('error', message, extra); }
177
- fatal(message: string, extra?: Record<string, unknown>): void { this._emit('fatal', message, extra); }
178
- ```
179
-
180
- ### 11. Run tests and verify all pass
181
-
182
- ## Acceptance Criteria
183
-
184
- - [x] `ContextLogger` supports JSON (default) and text output formats
185
- - [x] 6 log levels: trace (0), debug (10), info (20), warn (30), error (40), fatal (50)
186
- - [x] Level filtering suppresses messages below the configured minimum level
187
- - [x] `_secret_`-prefixed extra keys redacted to `***REDACTED***` when `redactSensitive` is true (default)
188
- - [x] Redaction can be disabled via `redactSensitive: false`
189
- - [x] `fromContext()` binds `traceId`, `moduleId` (last in callChain), and `callerId` from a Context
190
- - [x] JSON format includes: timestamp, level, message, trace_id, module_id, caller_id, logger, extra
191
- - [x] Text format: `timestamp [LEVEL] [trace=...] [module=...] message extras`
192
- - [x] Default output is `process.stderr`; custom `WritableOutput` accepted via options
193
- - [x] All tests pass with `vitest`
194
-
195
- ## Dependencies
196
-
197
- - None (independent pillar, but uses `Context` from core-executor for `fromContext()`)
198
-
199
- ## Estimated Time
200
-
201
- 2 hours
@@ -1,121 +0,0 @@
1
- # Task: StdoutExporter and InMemoryExporter
2
-
3
- ## Goal
4
-
5
- Implement two `SpanExporter` implementations: `StdoutExporter` that serializes spans to stdout via `JSON.stringify`, and `InMemoryExporter` that stores spans in a bounded array with FIFO eviction. Note: `OTLPExporter` (OpenTelemetry Protocol) is not implemented -- this is a known gap for future work.
6
-
7
- ## Files Involved
8
-
9
- - `src/observability/tracing.ts` -- StdoutExporter and InMemoryExporter classes
10
- - `tests/observability/test-tracing.test.ts` -- Unit tests for both exporters
11
-
12
- ## Steps (TDD)
13
-
14
- ### 1. Write failing tests for InMemoryExporter
15
-
16
- ```typescript
17
- describe('InMemoryExporter', () => {
18
- it('collects and retrieves spans', () => {
19
- const exporter = new InMemoryExporter();
20
- const span = createSpan({ traceId: 't1', name: 'test', startTime: 0 });
21
- exporter.export(span);
22
- expect(exporter.getSpans()).toHaveLength(1);
23
- expect(exporter.getSpans()[0].traceId).toBe('t1');
24
- });
25
-
26
- it('respects max_spans limit', () => {
27
- const exporter = new InMemoryExporter(3);
28
- for (let i = 0; i < 5; i++) {
29
- exporter.export(createSpan({ traceId: `t${i}`, name: 'test', startTime: i }));
30
- }
31
- const spans = exporter.getSpans();
32
- expect(spans).toHaveLength(3);
33
- expect(spans[0].traceId).toBe('t2'); // oldest evicted
34
- });
35
-
36
- it('clear removes all spans', () => {
37
- const exporter = new InMemoryExporter();
38
- exporter.export(createSpan({ traceId: 't1', name: 'test', startTime: 0 }));
39
- exporter.clear();
40
- expect(exporter.getSpans()).toHaveLength(0);
41
- });
42
-
43
- it('getSpans returns defensive copy', () => {
44
- const exporter = new InMemoryExporter();
45
- exporter.export(createSpan({ traceId: 't1', name: 'test', startTime: 0 }));
46
- const spans = exporter.getSpans();
47
- spans.pop(); // mutate the copy
48
- expect(exporter.getSpans()).toHaveLength(1); // original unaffected
49
- });
50
- });
51
- ```
52
-
53
- ### 2. Implement StdoutExporter
54
-
55
- ```typescript
56
- export class StdoutExporter implements SpanExporter {
57
- export(span: Span): void {
58
- console.log(JSON.stringify(span));
59
- }
60
- }
61
- ```
62
-
63
- Simple passthrough: serialize the span as a JSON string and write to stdout via `console.log`. No buffering or batching.
64
-
65
- ### 3. Implement InMemoryExporter
66
-
67
- ```typescript
68
- export class InMemoryExporter implements SpanExporter {
69
- private _spans: Span[] = [];
70
- private _maxSpans: number;
71
-
72
- constructor(maxSpans: number = 10_000) {
73
- this._maxSpans = maxSpans;
74
- }
75
-
76
- export(span: Span): void {
77
- this._spans.push(span);
78
- while (this._spans.length > this._maxSpans) {
79
- this._spans.shift(); // FIFO eviction
80
- }
81
- }
82
-
83
- getSpans(): Span[] {
84
- return [...this._spans]; // defensive copy
85
- }
86
-
87
- clear(): void {
88
- this._spans = [];
89
- }
90
- }
91
- ```
92
-
93
- Key design decisions:
94
- - Default `maxSpans` of 10,000 (configurable via constructor)
95
- - `shift()` for FIFO eviction (O(n) but acceptable for expected volumes, unlike Python's O(1) `deque.popleft()`)
96
- - `getSpans()` returns a spread copy to prevent external mutation of internal state
97
- - No thread locking needed (Node.js single-threaded)
98
-
99
- ### 4. Run tests and verify all pass
100
-
101
- ## Known Gap: OTLPExporter
102
-
103
- The Python implementation includes an `OTLPExporter` for shipping spans to OpenTelemetry-compatible backends. This is **not yet implemented** in the TypeScript version. The `SpanExporter` interface is designed to be forward-compatible -- a future `OTLPExporter` would implement `export(span: Span): void` and handle HTTP/gRPC transport to an OTLP collector.
104
-
105
- ## Acceptance Criteria
106
-
107
- - [x] `StdoutExporter` implements `SpanExporter` and calls `console.log(JSON.stringify(span))`
108
- - [x] `InMemoryExporter` stores spans in a bounded array with configurable `maxSpans` (default 10,000)
109
- - [x] FIFO eviction via `shift()` when array exceeds `maxSpans`
110
- - [x] `getSpans()` returns a defensive copy via spread operator
111
- - [x] `clear()` empties the internal span array
112
- - [x] OTLPExporter absence is documented as a known gap
113
- - [x] All tests pass with `vitest`
114
-
115
- ## Dependencies
116
-
117
- - **span-model** -- Requires `Span` interface and `SpanExporter` interface
118
-
119
- ## Estimated Time
120
-
121
- 1.5 hours