@ontrails/core 1.0.0-beta.15 → 1.0.0-beta.17

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 (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +64 -19
  3. package/package.json +9 -8
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/context.ts +4 -0
  10. package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
  11. package/src/detours.ts +8 -0
  12. package/src/draft.ts +4 -0
  13. package/src/error-projection.ts +51 -0
  14. package/src/errors.ts +192 -39
  15. package/src/execute.ts +443 -44
  16. package/src/fire.ts +965 -91
  17. package/src/index.ts +285 -38
  18. package/src/layer-projection.ts +192 -0
  19. package/src/layer.ts +32 -1
  20. package/src/observe.ts +361 -0
  21. package/src/permits.ts +12 -0
  22. package/src/redaction/patterns.ts +6 -3
  23. package/src/resilience.ts +8 -8
  24. package/src/resource-config.ts +477 -44
  25. package/src/resource.ts +21 -1
  26. package/src/result.ts +18 -0
  27. package/src/run.ts +2 -2
  28. package/src/schedule-runtime.ts +689 -0
  29. package/src/schedule.ts +324 -0
  30. package/src/serialization.ts +90 -57
  31. package/src/signal-diagnostics.ts +633 -0
  32. package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
  33. package/src/signal.ts +37 -0
  34. package/src/store/accessor-protocol.ts +5 -5
  35. package/src/structured-examples.ts +245 -0
  36. package/src/surface-derivation.ts +91 -0
  37. package/src/surface-filter.ts +7 -13
  38. package/src/topo.ts +386 -6
  39. package/src/tracing.ts +345 -0
  40. package/src/trail.ts +168 -22
  41. package/src/trails/derive-trail.ts +1 -1
  42. package/src/trails/ingest.ts +7 -15
  43. package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
  44. package/src/transport-error-map.ts +146 -28
  45. package/src/types.ts +82 -28
  46. package/src/validate-established-topo.ts +6 -0
  47. package/src/validate-topo.ts +212 -5
  48. package/src/validation.ts +58 -1
  49. package/src/webhook.ts +276 -0
  50. package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
  51. package/.turbo/turbo-build.log +0 -1
  52. package/.turbo/turbo-lint.log +0 -3
  53. package/.turbo/turbo-typecheck.log +0 -1
  54. package/dist/blob-ref.d.ts +0 -20
  55. package/dist/blob-ref.d.ts.map +0 -1
  56. package/dist/blob-ref.js +0 -22
  57. package/dist/blob-ref.js.map +0 -1
  58. package/dist/branded.d.ts +0 -36
  59. package/dist/branded.d.ts.map +0 -1
  60. package/dist/branded.js +0 -89
  61. package/dist/branded.js.map +0 -1
  62. package/dist/collections.d.ts +0 -31
  63. package/dist/collections.d.ts.map +0 -1
  64. package/dist/collections.js +0 -60
  65. package/dist/collections.js.map +0 -1
  66. package/dist/context.d.ts +0 -22
  67. package/dist/context.d.ts.map +0 -1
  68. package/dist/context.js +0 -37
  69. package/dist/context.js.map +0 -1
  70. package/dist/contour.d.ts +0 -81
  71. package/dist/contour.d.ts.map +0 -1
  72. package/dist/contour.js +0 -176
  73. package/dist/contour.js.map +0 -1
  74. package/dist/cross-schema.d.ts +0 -18
  75. package/dist/cross-schema.d.ts.map +0 -1
  76. package/dist/cross-schema.js +0 -29
  77. package/dist/cross-schema.js.map +0 -1
  78. package/dist/derive.d.ts +0 -44
  79. package/dist/derive.d.ts.map +0 -1
  80. package/dist/derive.js +0 -149
  81. package/dist/derive.js.map +0 -1
  82. package/dist/draft.d.ts +0 -28
  83. package/dist/draft.d.ts.map +0 -1
  84. package/dist/draft.js +0 -157
  85. package/dist/draft.js.map +0 -1
  86. package/dist/errors.d.ts +0 -141
  87. package/dist/errors.d.ts.map +0 -1
  88. package/dist/errors.js +0 -185
  89. package/dist/errors.js.map +0 -1
  90. package/dist/execute.d.ts +0 -52
  91. package/dist/execute.d.ts.map +0 -1
  92. package/dist/execute.js +0 -511
  93. package/dist/execute.js.map +0 -1
  94. package/dist/fetch.d.ts +0 -15
  95. package/dist/fetch.d.ts.map +0 -1
  96. package/dist/fetch.js +0 -102
  97. package/dist/fetch.js.map +0 -1
  98. package/dist/fire.d.ts +0 -43
  99. package/dist/fire.d.ts.map +0 -1
  100. package/dist/fire.js +0 -185
  101. package/dist/fire.js.map +0 -1
  102. package/dist/guards.d.ts +0 -17
  103. package/dist/guards.d.ts.map +0 -1
  104. package/dist/guards.js +0 -25
  105. package/dist/guards.js.map +0 -1
  106. package/dist/index.d.ts +0 -59
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js +0 -61
  109. package/dist/index.js.map +0 -1
  110. package/dist/internal/cross-batch.d.ts +0 -44
  111. package/dist/internal/cross-batch.d.ts.map +0 -1
  112. package/dist/internal/cross-batch.js +0 -58
  113. package/dist/internal/cross-batch.js.map +0 -1
  114. package/dist/internal/fork-ctx.d.ts +0 -41
  115. package/dist/internal/fork-ctx.d.ts.map +0 -1
  116. package/dist/internal/fork-ctx.js +0 -47
  117. package/dist/internal/fork-ctx.js.map +0 -1
  118. package/dist/internal/signal-ref.d.ts +0 -15
  119. package/dist/internal/signal-ref.d.ts.map +0 -1
  120. package/dist/internal/signal-ref.js +0 -41
  121. package/dist/internal/signal-ref.js.map +0 -1
  122. package/dist/internal/topo-saves.d.ts +0 -53
  123. package/dist/internal/topo-saves.d.ts.map +0 -1
  124. package/dist/internal/topo-saves.js +0 -449
  125. package/dist/internal/topo-saves.js.map +0 -1
  126. package/dist/internal/topo-snapshots.d.ts +0 -57
  127. package/dist/internal/topo-snapshots.d.ts.map +0 -1
  128. package/dist/internal/topo-snapshots.js +0 -372
  129. package/dist/internal/topo-snapshots.js.map +0 -1
  130. package/dist/internal/topo-store-read.d.ts +0 -70
  131. package/dist/internal/topo-store-read.d.ts.map +0 -1
  132. package/dist/internal/topo-store-read.js +0 -221
  133. package/dist/internal/topo-store-read.js.map +0 -1
  134. package/dist/internal/topo-store.d.ts +0 -26
  135. package/dist/internal/topo-store.d.ts.map +0 -1
  136. package/dist/internal/topo-store.js +0 -621
  137. package/dist/internal/topo-store.js.map +0 -1
  138. package/dist/internal/tracing.d.ts +0 -98
  139. package/dist/internal/tracing.d.ts.map +0 -1
  140. package/dist/internal/tracing.js +0 -102
  141. package/dist/internal/tracing.js.map +0 -1
  142. package/dist/internal/trails-db.d.ts +0 -16
  143. package/dist/internal/trails-db.d.ts.map +0 -1
  144. package/dist/internal/trails-db.js +0 -118
  145. package/dist/internal/trails-db.js.map +0 -1
  146. package/dist/internal/zod-wrappers.d.ts +0 -40
  147. package/dist/internal/zod-wrappers.d.ts.map +0 -1
  148. package/dist/internal/zod-wrappers.js +0 -59
  149. package/dist/internal/zod-wrappers.js.map +0 -1
  150. package/dist/layer.d.ts +0 -23
  151. package/dist/layer.d.ts.map +0 -1
  152. package/dist/layer.js +0 -21
  153. package/dist/layer.js.map +0 -1
  154. package/dist/path-security.d.ts +0 -28
  155. package/dist/path-security.d.ts.map +0 -1
  156. package/dist/path-security.js +0 -63
  157. package/dist/path-security.js.map +0 -1
  158. package/dist/patterns/bulk.d.ts +0 -15
  159. package/dist/patterns/bulk.d.ts.map +0 -1
  160. package/dist/patterns/bulk.js +0 -14
  161. package/dist/patterns/bulk.js.map +0 -1
  162. package/dist/patterns/change.d.ts +0 -10
  163. package/dist/patterns/change.d.ts.map +0 -1
  164. package/dist/patterns/change.js +0 -10
  165. package/dist/patterns/change.js.map +0 -1
  166. package/dist/patterns/date-range.d.ts +0 -10
  167. package/dist/patterns/date-range.d.ts.map +0 -1
  168. package/dist/patterns/date-range.js +0 -10
  169. package/dist/patterns/date-range.js.map +0 -1
  170. package/dist/patterns/index.d.ts +0 -9
  171. package/dist/patterns/index.d.ts.map +0 -1
  172. package/dist/patterns/index.js +0 -9
  173. package/dist/patterns/index.js.map +0 -1
  174. package/dist/patterns/pagination.d.ts +0 -18
  175. package/dist/patterns/pagination.d.ts.map +0 -1
  176. package/dist/patterns/pagination.js +0 -18
  177. package/dist/patterns/pagination.js.map +0 -1
  178. package/dist/patterns/progress.d.ts +0 -11
  179. package/dist/patterns/progress.d.ts.map +0 -1
  180. package/dist/patterns/progress.js +0 -11
  181. package/dist/patterns/progress.js.map +0 -1
  182. package/dist/patterns/sorting.d.ts +0 -13
  183. package/dist/patterns/sorting.d.ts.map +0 -1
  184. package/dist/patterns/sorting.js +0 -10
  185. package/dist/patterns/sorting.js.map +0 -1
  186. package/dist/patterns/status.d.ts +0 -15
  187. package/dist/patterns/status.d.ts.map +0 -1
  188. package/dist/patterns/status.js +0 -9
  189. package/dist/patterns/status.js.map +0 -1
  190. package/dist/patterns/timestamps.d.ts +0 -10
  191. package/dist/patterns/timestamps.d.ts.map +0 -1
  192. package/dist/patterns/timestamps.js +0 -10
  193. package/dist/patterns/timestamps.js.map +0 -1
  194. package/dist/redaction/index.d.ts +0 -4
  195. package/dist/redaction/index.d.ts.map +0 -1
  196. package/dist/redaction/index.js +0 -3
  197. package/dist/redaction/index.js.map +0 -1
  198. package/dist/redaction/patterns.d.ts +0 -9
  199. package/dist/redaction/patterns.d.ts.map +0 -1
  200. package/dist/redaction/patterns.js +0 -39
  201. package/dist/redaction/patterns.js.map +0 -1
  202. package/dist/redaction/redactor.d.ts +0 -27
  203. package/dist/redaction/redactor.d.ts.map +0 -1
  204. package/dist/redaction/redactor.js +0 -89
  205. package/dist/redaction/redactor.js.map +0 -1
  206. package/dist/resilience.d.ts +0 -34
  207. package/dist/resilience.d.ts.map +0 -1
  208. package/dist/resilience.js +0 -164
  209. package/dist/resilience.js.map +0 -1
  210. package/dist/resource-config.d.ts +0 -22
  211. package/dist/resource-config.d.ts.map +0 -1
  212. package/dist/resource-config.js +0 -210
  213. package/dist/resource-config.js.map +0 -1
  214. package/dist/resource.d.ts +0 -74
  215. package/dist/resource.d.ts.map +0 -1
  216. package/dist/resource.js +0 -61
  217. package/dist/resource.js.map +0 -1
  218. package/dist/result.d.ts +0 -57
  219. package/dist/result.d.ts.map +0 -1
  220. package/dist/result.js +0 -156
  221. package/dist/result.js.map +0 -1
  222. package/dist/run.d.ts +0 -28
  223. package/dist/run.d.ts.map +0 -1
  224. package/dist/run.js +0 -35
  225. package/dist/run.js.map +0 -1
  226. package/dist/serialization.d.ts +0 -27
  227. package/dist/serialization.d.ts.map +0 -1
  228. package/dist/serialization.js +0 -154
  229. package/dist/serialization.js.map +0 -1
  230. package/dist/signal.d.ts +0 -33
  231. package/dist/signal.d.ts.map +0 -1
  232. package/dist/signal.js +0 -17
  233. package/dist/signal.js.map +0 -1
  234. package/dist/store/accessor-protocol.d.ts +0 -51
  235. package/dist/store/accessor-protocol.d.ts.map +0 -1
  236. package/dist/store/accessor-protocol.js +0 -2
  237. package/dist/store/accessor-protocol.js.map +0 -1
  238. package/dist/store/index.d.ts +0 -2
  239. package/dist/store/index.d.ts.map +0 -1
  240. package/dist/store/index.js +0 -2
  241. package/dist/store/index.js.map +0 -1
  242. package/dist/surface-filter.d.ts +0 -13
  243. package/dist/surface-filter.d.ts.map +0 -1
  244. package/dist/surface-filter.js +0 -76
  245. package/dist/surface-filter.js.map +0 -1
  246. package/dist/topo-store.d.ts +0 -62
  247. package/dist/topo-store.d.ts.map +0 -1
  248. package/dist/topo-store.js +0 -319
  249. package/dist/topo-store.js.map +0 -1
  250. package/dist/topo.d.ts +0 -39
  251. package/dist/topo.d.ts.map +0 -1
  252. package/dist/topo.js +0 -241
  253. package/dist/topo.js.map +0 -1
  254. package/dist/trail.d.ts +0 -163
  255. package/dist/trail.d.ts.map +0 -1
  256. package/dist/trail.js +0 -75
  257. package/dist/trail.js.map +0 -1
  258. package/dist/trails/derive-trail.d.ts +0 -66
  259. package/dist/trails/derive-trail.d.ts.map +0 -1
  260. package/dist/trails/derive-trail.js +0 -378
  261. package/dist/trails/derive-trail.js.map +0 -1
  262. package/dist/trails/index.d.ts +0 -5
  263. package/dist/trails/index.d.ts.map +0 -1
  264. package/dist/trails/index.js +0 -3
  265. package/dist/trails/index.js.map +0 -1
  266. package/dist/trails/ingest.d.ts +0 -24
  267. package/dist/trails/ingest.d.ts.map +0 -1
  268. package/dist/trails/ingest.js +0 -63
  269. package/dist/trails/ingest.js.map +0 -1
  270. package/dist/transport-error-map.d.ts +0 -106
  271. package/dist/transport-error-map.d.ts.map +0 -1
  272. package/dist/transport-error-map.js +0 -24
  273. package/dist/transport-error-map.js.map +0 -1
  274. package/dist/type-checks.test-d.d.ts +0 -71
  275. package/dist/type-checks.test-d.d.ts.map +0 -1
  276. package/dist/type-checks.test-d.js +0 -12
  277. package/dist/type-checks.test-d.js.map +0 -1
  278. package/dist/type-utils.d.ts +0 -33
  279. package/dist/type-utils.d.ts.map +0 -1
  280. package/dist/type-utils.js +0 -12
  281. package/dist/type-utils.js.map +0 -1
  282. package/dist/types.d.ts +0 -177
  283. package/dist/types.d.ts.map +0 -1
  284. package/dist/types.js +0 -3
  285. package/dist/types.js.map +0 -1
  286. package/dist/validate-established-topo.d.ts +0 -76
  287. package/dist/validate-established-topo.d.ts.map +0 -1
  288. package/dist/validate-established-topo.js +0 -43
  289. package/dist/validate-established-topo.js.map +0 -1
  290. package/dist/validate-topo.d.ts +0 -24
  291. package/dist/validate-topo.d.ts.map +0 -1
  292. package/dist/validate-topo.js +0 -193
  293. package/dist/validate-topo.js.map +0 -1
  294. package/dist/validation.d.ts +0 -27
  295. package/dist/validation.d.ts.map +0 -1
  296. package/dist/validation.js +0 -165
  297. package/dist/validation.js.map +0 -1
  298. package/dist/workspace.d.ts +0 -25
  299. package/dist/workspace.d.ts.map +0 -1
  300. package/dist/workspace.js +0 -57
  301. package/dist/workspace.js.map +0 -1
  302. package/src/__tests__/blob-ref.test.ts +0 -103
  303. package/src/__tests__/branded.test.ts +0 -148
  304. package/src/__tests__/collections.test.ts +0 -126
  305. package/src/__tests__/context.test.ts +0 -77
  306. package/src/__tests__/contour.test.ts +0 -263
  307. package/src/__tests__/derive-trail.test.ts +0 -668
  308. package/src/__tests__/derive.test.ts +0 -260
  309. package/src/__tests__/errors.test.ts +0 -427
  310. package/src/__tests__/execute.test.ts +0 -1563
  311. package/src/__tests__/fetch.test.ts +0 -217
  312. package/src/__tests__/fire.test.ts +0 -1056
  313. package/src/__tests__/fork-ctx.test.ts +0 -83
  314. package/src/__tests__/guards.test.ts +0 -102
  315. package/src/__tests__/ingest.test.ts +0 -191
  316. package/src/__tests__/layer.test.ts +0 -125
  317. package/src/__tests__/path-security.test.ts +0 -114
  318. package/src/__tests__/patterns.test.ts +0 -273
  319. package/src/__tests__/redaction.test.ts +0 -244
  320. package/src/__tests__/resilience.test.ts +0 -246
  321. package/src/__tests__/resource.test.ts +0 -204
  322. package/src/__tests__/result.test.ts +0 -155
  323. package/src/__tests__/run.test.ts +0 -181
  324. package/src/__tests__/serialization.test.ts +0 -401
  325. package/src/__tests__/service-config.test.ts +0 -224
  326. package/src/__tests__/signal.test.ts +0 -82
  327. package/src/__tests__/surface-filter.test.ts +0 -196
  328. package/src/__tests__/topo-store-read.test.ts +0 -256
  329. package/src/__tests__/topo-store.test.ts +0 -869
  330. package/src/__tests__/topo.test.ts +0 -573
  331. package/src/__tests__/trail-permit.test.ts +0 -60
  332. package/src/__tests__/trail.test.ts +0 -426
  333. package/src/__tests__/trails-db.test.ts +0 -198
  334. package/src/__tests__/transport-error-map.test.ts +0 -82
  335. package/src/__tests__/type-utils.test.ts +0 -118
  336. package/src/__tests__/validate-topo.test.ts +0 -611
  337. package/src/__tests__/validation.test.ts +0 -336
  338. package/src/__tests__/workspace.test.ts +0 -183
  339. package/src/__tests__/zod-wrappers.test.ts +0 -62
  340. package/src/internal/topo-snapshots.ts +0 -517
  341. package/src/internal/topo-store-read.ts +0 -481
  342. package/src/internal/topo-store.ts +0 -1192
  343. package/src/internal/tracing.ts +0 -187
  344. package/src/topo-store.ts +0 -510
  345. package/src/type-checks.test-d.ts +0 -106
  346. package/tsconfig.json +0 -9
  347. package/tsconfig.tests.json +0 -10
  348. package/tsconfig.tsbuildinfo +0 -1
package/src/observe.ts ADDED
@@ -0,0 +1,361 @@
1
+ import { ValidationError } from './errors.js';
2
+ import type { TraceSink } from './tracing.js';
3
+ import type { Layer } from './layer.js';
4
+ import { safeStringify } from './serialization.js';
5
+ import type { Logger, LogLevel, LogRecord, LogSink } from './types.js';
6
+
7
+ export interface ObserveConfig {
8
+ readonly log?: Logger | LogSink | undefined;
9
+ readonly trace?: TraceSink | undefined;
10
+ }
11
+
12
+ export interface ObserveCapabilities {
13
+ readonly log?: true | undefined;
14
+ readonly trace?: true | undefined;
15
+ }
16
+
17
+ interface ObserveCapable {
18
+ readonly observes?: ObserveCapabilities | undefined;
19
+ }
20
+
21
+ export type ObserveInput = Logger | LogSink | TraceSink | ObserveConfig;
22
+
23
+ export interface TopoOptions {
24
+ readonly observe?: ObserveInput | undefined;
25
+ /**
26
+ * Typed layers attached at topo scope.
27
+ *
28
+ * Layers declared here wrap every trail invoked through this topo, on every
29
+ * surface. The execution pipeline composes topo-scope layers outermost —
30
+ * around surface-scope and trail-scope layers — so the final order is
31
+ * `topo → surface → trail → blaze` (outermost-first).
32
+ */
33
+ readonly layers?: readonly Layer[] | undefined;
34
+ }
35
+
36
+ const OBSERVE_CONFIG_KEYS = new Set(['log', 'trace']);
37
+ export const OBSERVE_LOGGER_CONTEXT_KEY = '__trails_observe_logger';
38
+
39
+ /**
40
+ * Context extension key that carries metadata accumulated on the observe
41
+ * logger across rebindings (e.g. signal fan-out metadata such as `consumerId`
42
+ * and `signalId`). When `applyTopoObserveContext` rebinds the logger for a
43
+ * new trail, it merges this metadata into the freshly built observe logger
44
+ * so consumer log records retain provenance back to the triggering signal.
45
+ */
46
+ export const OBSERVE_LOGGER_METADATA_KEY = '__trails_observe_logger_metadata';
47
+
48
+ const isObject = (value: unknown): value is Record<string, unknown> =>
49
+ typeof value === 'object' && value !== null;
50
+
51
+ const hasFunction = (value: Record<string, unknown>, key: string): boolean =>
52
+ typeof value[key] === 'function';
53
+
54
+ export const isLogger = (value: unknown): value is Logger =>
55
+ isObject(value) &&
56
+ hasFunction(value, 'child') &&
57
+ hasFunction(value, 'debug') &&
58
+ hasFunction(value, 'error') &&
59
+ hasFunction(value, 'fatal') &&
60
+ hasFunction(value, 'info') &&
61
+ hasFunction(value, 'trace') &&
62
+ hasFunction(value, 'warn');
63
+
64
+ export const isLogSink = (value: unknown): value is LogSink =>
65
+ isObject(value) &&
66
+ typeof value['name'] === 'string' &&
67
+ hasFunction(value, 'write');
68
+
69
+ const readObserveCapabilities = (
70
+ value: unknown
71
+ ): ObserveCapabilities | undefined => {
72
+ if (!isObject(value)) {
73
+ return undefined;
74
+ }
75
+ const capabilities = (value as ObserveCapable).observes;
76
+ if (!isObject(capabilities)) {
77
+ return undefined;
78
+ }
79
+ const log = capabilities['log'] === true;
80
+ const trace = capabilities['trace'] === true;
81
+ if (!log && !trace) {
82
+ return undefined;
83
+ }
84
+ return Object.freeze({
85
+ ...(log ? { log: true as const } : {}),
86
+ ...(trace ? { trace: true as const } : {}),
87
+ });
88
+ };
89
+
90
+ export const isTraceSink = (value: unknown): value is TraceSink =>
91
+ isObject(value) && hasFunction(value, 'write');
92
+
93
+ const isObserveConfigShape = (value: unknown): value is ObserveConfig => {
94
+ if (!isObject(value)) {
95
+ return false;
96
+ }
97
+ const keys = Object.keys(value);
98
+ return (
99
+ keys.length > 0 &&
100
+ keys.every((key) => OBSERVE_CONFIG_KEYS.has(key)) &&
101
+ ('log' in value || 'trace' in value)
102
+ );
103
+ };
104
+
105
+ /**
106
+ * Type guard for the `ObserveInput` union. Returns `true` only for shapes
107
+ * that {@link normalizeObserve} will accept without throwing — i.e. a
108
+ * `Logger`, an explicit `ObserveConfig`, or a `TraceSink`. Capability-only
109
+ * payloads (`{ observes: { trace: true } }` without an accompanying
110
+ * `write` method) and bare `LogSink` shorthand are intentionally rejected:
111
+ * the former is metadata about a missing implementation, and the latter
112
+ * is ambiguous between a `LogSink` and a `TraceSink` and is rejected by
113
+ * `normalizeObserve` accordingly. Keeping the guard tighter than the
114
+ * runtime accepts would let callers narrow to `ObserveInput` and then
115
+ * see `normalizeObserve` throw at runtime.
116
+ */
117
+ export const isObserveInput = (
118
+ value: unknown
119
+ ): value is ObserveInput | undefined => {
120
+ if (value === undefined) {
121
+ return true;
122
+ }
123
+ if (isLogger(value)) {
124
+ return true;
125
+ }
126
+ if (isObserveConfigShape(value)) {
127
+ return true;
128
+ }
129
+ if (isTraceSink(value) && !isLogSink(value)) {
130
+ // A bare TraceSink (no `name`) is unambiguous — `normalizeObserve`
131
+ // routes it via the `isTraceSink` fallthrough. A LogSink shape is
132
+ // ambiguous (matches both guards) and would be rejected by
133
+ // `normalizeObserve`, so the guard rejects it here too.
134
+ return true;
135
+ }
136
+ return false;
137
+ };
138
+
139
+ /**
140
+ * Returns true when `value` carries explicit observe capabilities via the
141
+ * `observes` discriminator. Used by the topo classifier to distinguish
142
+ * an `ObserveCapable` sink (clearly options) from a bare sink (ambiguous
143
+ * with a module export named `observe`).
144
+ */
145
+ export const hasObserveCapabilities = (value: unknown): boolean =>
146
+ readObserveCapabilities(value) !== undefined;
147
+
148
+ /**
149
+ * Returns true when `value` is shaped like the explicit `{ log?, trace? }`
150
+ * `ObserveConfig` payload. Exposed for the topo classifier so a config-style
151
+ * trailing argument is unambiguously routed to options.
152
+ */
153
+ export const isObserveConfig = (value: unknown): value is ObserveConfig =>
154
+ isObserveConfigShape(value);
155
+
156
+ const normalizeLogTarget = (
157
+ target: ObserveConfig['log']
158
+ ): ObserveConfig['log'] => {
159
+ if (target === undefined || isLogger(target) || isLogSink(target)) {
160
+ return target;
161
+ }
162
+ throw new ValidationError('topo observe.log must be a Logger or LogSink');
163
+ };
164
+
165
+ const normalizeTraceTarget = (
166
+ target: ObserveConfig['trace']
167
+ ): ObserveConfig['trace'] => {
168
+ if (target === undefined || isTraceSink(target)) {
169
+ return target;
170
+ }
171
+ throw new ValidationError('topo observe.trace must be a TraceSink');
172
+ };
173
+
174
+ /**
175
+ * Maps observe log levels to the `console` method that should receive the
176
+ * formatted record. `silent` is intentionally absent — records at that level
177
+ * are dropped before reaching `console`.
178
+ */
179
+ const DEFAULT_SINK_CONSOLE_METHOD: Record<
180
+ LogLevel,
181
+ 'debug' | 'error' | 'info' | 'warn' | undefined
182
+ > = {
183
+ debug: 'debug',
184
+ error: 'error',
185
+ fatal: 'error',
186
+ info: 'info',
187
+ silent: undefined,
188
+ trace: 'debug',
189
+ warn: 'warn',
190
+ };
191
+
192
+ const stringifyDefaultConsoleRecord = (record: LogRecord): string => {
193
+ const serialized = safeStringify({
194
+ category: record.category,
195
+ level: record.level,
196
+ message: record.message,
197
+ metadata: record.metadata,
198
+ timestamp: record.timestamp.toISOString(),
199
+ });
200
+ if (serialized.isOk()) {
201
+ return serialized.value;
202
+ }
203
+ return JSON.stringify({
204
+ category: record.category,
205
+ level: record.level,
206
+ message: record.message,
207
+ metadata: '[unserializable]',
208
+ timestamp: record.timestamp.toISOString(),
209
+ });
210
+ };
211
+
212
+ /**
213
+ * In-core mirror of `@ontrails/observe`'s `createConsoleSink` shape, kept
214
+ * minimal and private to avoid a reverse dependency from `@ontrails/core`
215
+ * onto `@ontrails/observe`. It mirrors the console level routing in
216
+ * `packages/observe/src/sinks.ts:50` and emits each record as a single-line
217
+ * JSON object routed to the matching `console.{debug|info|warn|error}` method.
218
+ *
219
+ * @remarks
220
+ * Used as the default `observe.log` target when `topo()` is called without an
221
+ * explicit `observe` option, so every app gets a non-null `ctx.logger` with
222
+ * structured stdout output and zero configuration. Apps that want richer
223
+ * formatting, file destinations, or custom routing should pass an explicit
224
+ * `observe` option, which fully replaces this default.
225
+ */
226
+ const createDefaultConsoleSink = (): LogSink => ({
227
+ name: 'console',
228
+ write(record): void {
229
+ const method = DEFAULT_SINK_CONSOLE_METHOD[record.level];
230
+ if (method === undefined) {
231
+ return;
232
+ }
233
+ const payload = stringifyDefaultConsoleRecord(record);
234
+ // oxlint-disable-next-line trails-local/no-console-in-packages -- ADR 0041 mandates a default console logger in core; this is the single sanctioned console boundary, mirroring `@ontrails/observe`'s `createConsoleSink`.
235
+ console[method](payload);
236
+ },
237
+ });
238
+
239
+ /**
240
+ * The default observe configuration applied when `topo()` receives no
241
+ * `observe` option. Frozen so callers cannot mutate the shared default; the
242
+ * sink itself is shared across topos because it has no per-topo state.
243
+ */
244
+ const DEFAULT_OBSERVE_CONFIG: ObserveConfig = Object.freeze({
245
+ log: createDefaultConsoleSink(),
246
+ });
247
+
248
+ export const normalizeObserve = (
249
+ observe: ObserveInput | undefined
250
+ ): ObserveConfig | undefined => {
251
+ if (observe === undefined) {
252
+ // ADR 0041 promises a non-null `ctx.logger` with zero configuration.
253
+ // Returning the default config here lets the existing topo → adapter
254
+ // path project this log sink into `ctx.logger` without a second
255
+ // resolution point or a reverse dependency on `@ontrails/observe`.
256
+ return DEFAULT_OBSERVE_CONFIG;
257
+ }
258
+
259
+ const capabilities = readObserveCapabilities(observe);
260
+ if (capabilities !== undefined) {
261
+ const log =
262
+ capabilities.log === true
263
+ ? normalizeLogTarget(observe as Logger | LogSink)
264
+ : undefined;
265
+ const trace =
266
+ capabilities.trace === true
267
+ ? normalizeTraceTarget(observe as TraceSink)
268
+ : undefined;
269
+ return Object.freeze({
270
+ ...(log === undefined ? {} : { log }),
271
+ ...(trace === undefined ? {} : { trace }),
272
+ });
273
+ }
274
+
275
+ if (isLogger(observe)) {
276
+ return Object.freeze({ log: observe });
277
+ }
278
+
279
+ if (isObserveConfigShape(observe)) {
280
+ const log = normalizeLogTarget(observe.log);
281
+ const trace = normalizeTraceTarget(observe.trace);
282
+ if (log === undefined && trace === undefined) {
283
+ return undefined;
284
+ }
285
+ return Object.freeze({
286
+ ...(log === undefined ? {} : { log }),
287
+ ...(trace === undefined ? {} : { trace }),
288
+ });
289
+ }
290
+
291
+ if (isLogSink(observe)) {
292
+ throw new ValidationError(
293
+ 'topo observe shorthand is ambiguous for named sinks; use { log: sink } or { trace: sink }'
294
+ );
295
+ }
296
+
297
+ if (isTraceSink(observe)) {
298
+ return Object.freeze({ trace: observe });
299
+ }
300
+
301
+ throw new ValidationError(
302
+ 'topo observe must be a Logger, LogSink, TraceSink, or { log, trace } object'
303
+ );
304
+ };
305
+
306
+ const createLogSinkLogger = (
307
+ sink: LogSink,
308
+ category: string,
309
+ baseMetadata: Record<string, unknown>
310
+ ): Logger => {
311
+ const write = (
312
+ level: LogLevel,
313
+ message: string,
314
+ metadata?: Record<string, unknown>
315
+ ): void => {
316
+ sink.write({
317
+ category,
318
+ level,
319
+ message,
320
+ metadata: { ...baseMetadata, ...metadata },
321
+ timestamp: new Date(),
322
+ });
323
+ };
324
+
325
+ return {
326
+ child(metadata: Record<string, unknown>): Logger {
327
+ return createLogSinkLogger(sink, category, {
328
+ ...baseMetadata,
329
+ ...metadata,
330
+ });
331
+ },
332
+ debug(message, metadata): void {
333
+ write('debug', message, metadata);
334
+ },
335
+ error(message, metadata): void {
336
+ write('error', message, metadata);
337
+ },
338
+ fatal(message, metadata): void {
339
+ write('fatal', message, metadata);
340
+ },
341
+ info(message, metadata): void {
342
+ write('info', message, metadata);
343
+ },
344
+ name: category,
345
+ trace(message, metadata): void {
346
+ write('trace', message, metadata);
347
+ },
348
+ warn(message, metadata): void {
349
+ write('warn', message, metadata);
350
+ },
351
+ };
352
+ };
353
+
354
+ export const createObserveLogger = (
355
+ log: Logger | LogSink,
356
+ category: string,
357
+ metadata: Record<string, unknown>
358
+ ): Logger =>
359
+ isLogger(log)
360
+ ? log.child(metadata)
361
+ : createLogSinkLogger(log, category, metadata);
package/src/permits.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Minimal permit shape available on TrailContext. */
4
+ export interface BasePermit {
5
+ readonly id: string;
6
+ readonly scopes: readonly string[];
7
+ }
8
+
9
+ export const basePermitSchema: z.ZodType<BasePermit> = z.object({
10
+ id: z.string(),
11
+ scopes: z.array(z.string()).readonly(),
12
+ });
@@ -16,11 +16,14 @@ export const DEFAULT_PATTERNS: RegExp[] = [
16
16
  // SSN: XXX-XX-XXXX
17
17
  /\b\d{3}-\d{2}-\d{4}\b/g,
18
18
 
19
- // Bearer tokens
20
- /Bearer\s+[A-Za-z0-9\-._~+/]+=*/g,
19
+ // Bearer tokens. Keep the threshold above short prose like "Bearer token".
20
+ /Bearer\s+[A-Za-z0-9\-._~+/=]{6,}/g,
21
21
 
22
22
  // Basic auth
23
- /Basic\s+[A-Za-z0-9+/]+=*/g,
23
+ /Basic\s+[A-Za-z0-9+/=]{8,}/g,
24
+
25
+ // Common key/value secrets embedded in strings
26
+ /\b(?:password|secret|token|api[_-]?key|cookie)\s*[:=]\s*[^\s,;]+/gi,
24
27
 
25
28
  // API keys: sk-*, pk_*, sk_* prefixed tokens
26
29
  /\b(?:sk-|pk_|sk_)[A-Za-z0-9_-]{8,}\b/g,
package/src/resilience.ts CHANGED
@@ -203,10 +203,9 @@ export const withTimeout = <T>(
203
203
  );
204
204
  }, ms);
205
205
 
206
- // oxlint-disable-next-line prefer-await-to-then, no-void -- .then() needed inside Promise constructor; void discards unhandled rejection
207
- void fn().then(
208
- // oxlint-disable-next-line prefer-await-to-callbacks -- callback required inside .then()
209
- (result) => {
206
+ const run = async () => {
207
+ try {
208
+ const result = await fn();
210
209
  if (settled) {
211
210
  return;
212
211
  }
@@ -215,9 +214,7 @@ export const withTimeout = <T>(
215
214
  signal?.removeEventListener('abort', onAbort);
216
215
  // oxlint-disable-next-line promise/no-multiple-resolved -- settled guard ensures single resolution
217
216
  resolve(result);
218
- },
219
- // oxlint-disable-next-line prefer-await-to-callbacks -- rejection handler required inside .then()
220
- (error: unknown) => {
217
+ } catch (error: unknown) {
221
218
  if (settled) {
222
219
  return;
223
220
  }
@@ -229,6 +226,9 @@ export const withTimeout = <T>(
229
226
  Result.err(error instanceof Error ? error : new Error(String(error)))
230
227
  );
231
228
  }
232
- );
229
+ };
230
+
231
+ // oxlint-disable-next-line no-void -- fire-and-settle inside Promise constructor
232
+ void run();
233
233
  });
234
234
  };