harnery 0.36.0 → 0.37.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 (311) hide show
  1. package/dist/commander.js +4 -0
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +87 -46
  4. package/dist/commands/artifacts.d.ts.map +1 -1
  5. package/dist/commands/artifacts.js +26 -1
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +11 -0
  8. package/dist/commands/diagnostics.d.ts +4 -0
  9. package/dist/commands/diagnostics.d.ts.map +1 -0
  10. package/dist/commands/diagnostics.js +229 -0
  11. package/dist/commands/files.d.ts +12 -0
  12. package/dist/commands/files.d.ts.map +1 -1
  13. package/dist/commands/files.js +54 -1
  14. package/dist/commands/governor.d.ts.map +1 -1
  15. package/dist/commands/governor.js +4 -0
  16. package/dist/commands/ledger-v3.d.ts.map +1 -1
  17. package/dist/commands/ledger-v3.js +71 -2
  18. package/dist/commands/qa-run.d.ts +4 -0
  19. package/dist/commands/qa-run.d.ts.map +1 -0
  20. package/dist/commands/qa-run.js +141 -0
  21. package/dist/commands/resources.d.ts +4 -0
  22. package/dist/commands/resources.d.ts.map +1 -0
  23. package/dist/commands/resources.js +29 -0
  24. package/dist/commands/supervisor.d.ts +4 -0
  25. package/dist/commands/supervisor.d.ts.map +1 -0
  26. package/dist/commands/supervisor.js +101 -0
  27. package/dist/commands/web.d.ts.map +1 -1
  28. package/dist/commands/web.js +17 -0
  29. package/dist/commands/workflow.d.ts.map +1 -1
  30. package/dist/commands/workflow.js +4 -0
  31. package/dist/core/agents/cli.js +7 -3
  32. package/dist/core/agents/health.d.ts +17 -0
  33. package/dist/core/agents/health.d.ts.map +1 -0
  34. package/dist/core/agents/health.js +109 -0
  35. package/dist/core/agents/mailbox.d.ts +92 -0
  36. package/dist/core/agents/mailbox.d.ts.map +1 -0
  37. package/dist/core/agents/mailbox.js +325 -0
  38. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  39. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  40. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  41. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  42. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  43. package/dist/core/agents/render/prompt-context.js +34 -13
  44. package/dist/core/agents/render/session-context.d.ts +8 -0
  45. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  46. package/dist/core/agents/render/session-context.js +34 -3
  47. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  48. package/dist/core/agents/rules/commit-conflict.js +50 -1
  49. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  50. package/dist/core/agents/rules/stop-hook.js +6 -10
  51. package/dist/core/agents/session-finalizer-v3.js +4 -1
  52. package/dist/core/artifacts/index.d.ts +26 -1
  53. package/dist/core/artifacts/index.d.ts.map +1 -1
  54. package/dist/core/artifacts/index.js +146 -6
  55. package/dist/core/config.d.ts +12 -0
  56. package/dist/core/config.d.ts.map +1 -1
  57. package/dist/core/config.js +43 -0
  58. package/dist/core/diagnostics/advice.d.ts +9 -0
  59. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  60. package/dist/core/diagnostics/advice.js +112 -0
  61. package/dist/core/diagnostics/bundle.d.ts +35 -0
  62. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  63. package/dist/core/diagnostics/bundle.js +874 -0
  64. package/dist/core/diagnostics/comparison.d.ts +5 -0
  65. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  66. package/dist/core/diagnostics/comparison.js +444 -0
  67. package/dist/core/diagnostics/contract.d.ts +245 -0
  68. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  69. package/dist/core/diagnostics/contract.js +18 -0
  70. package/dist/core/diagnostics/identity.d.ts +3 -0
  71. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  72. package/dist/core/diagnostics/identity.js +10 -0
  73. package/dist/core/diagnostics/index.d.ts +8 -0
  74. package/dist/core/diagnostics/index.d.ts.map +1 -0
  75. package/dist/core/diagnostics/index.js +7 -0
  76. package/dist/core/diagnostics/replay.d.ts +8 -0
  77. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  78. package/dist/core/diagnostics/replay.js +213 -0
  79. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  80. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  81. package/dist/core/diagnostics/sanitize.js +84 -0
  82. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  83. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  84. package/dist/core/events/legacy-storage/compression.js +107 -0
  85. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  86. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  87. package/dist/core/events/legacy-storage/index.js +1 -0
  88. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  89. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  90. package/dist/core/events/v3/archive-retention.js +195 -0
  91. package/dist/core/events/v3/capabilities.d.ts +1 -1
  92. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  93. package/dist/core/events/v3/capabilities.js +1 -0
  94. package/dist/core/events/v3/index.d.ts +1 -0
  95. package/dist/core/events/v3/index.d.ts.map +1 -1
  96. package/dist/core/events/v3/index.js +1 -0
  97. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  98. package/dist/core/events/v3/live-routing.js +1 -0
  99. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  100. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  101. package/dist/core/events/v3/producers/hook-base.js +13 -2
  102. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  103. package/dist/core/events/v3/producers/intake.js +1 -0
  104. package/dist/core/events/v3/producers/recorder.d.ts +16 -0
  105. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  106. package/dist/core/events/v3/producers/recorder.js +88 -1
  107. package/dist/core/hooks/adapter/output.d.ts +8 -0
  108. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  109. package/dist/core/hooks/adapter/output.js +11 -1
  110. package/dist/core/hooks/cli.js +183 -192
  111. package/dist/core/hooks/health.d.ts +59 -0
  112. package/dist/core/hooks/health.d.ts.map +1 -0
  113. package/dist/core/hooks/health.js +84 -0
  114. package/dist/core/resources/contract.d.ts +90 -0
  115. package/dist/core/resources/contract.d.ts.map +1 -0
  116. package/dist/core/resources/contract.js +2 -0
  117. package/dist/core/resources/index.d.ts +6 -0
  118. package/dist/core/resources/index.d.ts.map +1 -0
  119. package/dist/core/resources/index.js +5 -0
  120. package/dist/core/resources/sampler.d.ts +27 -0
  121. package/dist/core/resources/sampler.d.ts.map +1 -0
  122. package/dist/core/resources/sampler.js +357 -0
  123. package/dist/core/resources/service-status.d.ts +3 -0
  124. package/dist/core/resources/service-status.d.ts.map +1 -0
  125. package/dist/core/resources/service-status.js +48 -0
  126. package/dist/core/resources/service.d.ts +26 -0
  127. package/dist/core/resources/service.d.ts.map +1 -0
  128. package/dist/core/resources/service.js +292 -0
  129. package/dist/core/resources/storage.d.ts +13 -0
  130. package/dist/core/resources/storage.d.ts.map +1 -0
  131. package/dist/core/resources/storage.js +30 -0
  132. package/dist/core/storage/atomic-json.d.ts +3 -0
  133. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  134. package/dist/core/storage/atomic-json.js +15 -0
  135. package/dist/core/storage/builtins.d.ts.map +1 -1
  136. package/dist/core/storage/builtins.js +45 -6
  137. package/dist/core/storage/logger.d.ts +2 -0
  138. package/dist/core/storage/logger.d.ts.map +1 -1
  139. package/dist/core/storage/logger.js +2 -0
  140. package/dist/core/storage/query.d.ts +14 -0
  141. package/dist/core/storage/query.d.ts.map +1 -1
  142. package/dist/core/storage/query.js +51 -0
  143. package/dist/core/supervisor/activity.d.ts +4 -0
  144. package/dist/core/supervisor/activity.d.ts.map +1 -0
  145. package/dist/core/supervisor/activity.js +141 -0
  146. package/dist/core/supervisor/contract.d.ts +268 -0
  147. package/dist/core/supervisor/contract.d.ts.map +1 -0
  148. package/dist/core/supervisor/contract.js +35 -0
  149. package/dist/core/supervisor/explanations.d.ts +4 -0
  150. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  151. package/dist/core/supervisor/explanations.js +71 -0
  152. package/dist/core/supervisor/findings.d.ts +20 -0
  153. package/dist/core/supervisor/findings.d.ts.map +1 -0
  154. package/dist/core/supervisor/findings.js +399 -0
  155. package/dist/core/supervisor/history.d.ts +9 -0
  156. package/dist/core/supervisor/history.d.ts.map +1 -0
  157. package/dist/core/supervisor/history.js +46 -0
  158. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  159. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  160. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  161. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  162. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  163. package/dist/core/supervisor/hook-health-storage.js +24 -0
  164. package/dist/core/supervisor/hook-health.d.ts +72 -0
  165. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  166. package/dist/core/supervisor/hook-health.js +203 -0
  167. package/dist/core/supervisor/hooks.d.ts +10 -0
  168. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  169. package/dist/core/supervisor/hooks.js +28 -0
  170. package/dist/core/supervisor/index.d.ts +15 -0
  171. package/dist/core/supervisor/index.d.ts.map +1 -0
  172. package/dist/core/supervisor/index.js +14 -0
  173. package/dist/core/supervisor/log-feed.d.ts +9 -0
  174. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  175. package/dist/core/supervisor/log-feed.js +103 -0
  176. package/dist/core/supervisor/service.d.ts +29 -0
  177. package/dist/core/supervisor/service.d.ts.map +1 -0
  178. package/dist/core/supervisor/service.js +450 -0
  179. package/dist/core/supervisor/services.d.ts +11 -0
  180. package/dist/core/supervisor/services.d.ts.map +1 -0
  181. package/dist/core/supervisor/services.js +155 -0
  182. package/dist/core/supervisor/status.d.ts +3 -0
  183. package/dist/core/supervisor/status.d.ts.map +1 -0
  184. package/dist/core/supervisor/status.js +48 -0
  185. package/dist/core/supervisor/storage.d.ts +30 -0
  186. package/dist/core/supervisor/storage.d.ts.map +1 -0
  187. package/dist/core/supervisor/storage.js +73 -0
  188. package/dist/core/supervisor/timeline.d.ts +3 -0
  189. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  190. package/dist/core/supervisor/timeline.js +87 -0
  191. package/dist/core/workflow/admission.d.ts +23 -0
  192. package/dist/core/workflow/admission.d.ts.map +1 -0
  193. package/dist/core/workflow/admission.js +136 -0
  194. package/dist/core/workflow/engine.d.ts.map +1 -1
  195. package/dist/core/workflow/engine.js +77 -2
  196. package/dist/core/workflow/index.d.ts +3 -2
  197. package/dist/core/workflow/index.d.ts.map +1 -1
  198. package/dist/core/workflow/index.js +2 -1
  199. package/dist/core/workflow/proof.d.ts +2 -1
  200. package/dist/core/workflow/proof.d.ts.map +1 -1
  201. package/dist/core/workflow/proof.js +39 -1
  202. package/dist/core/workflow/run-state.d.ts +3 -2
  203. package/dist/core/workflow/run-state.d.ts.map +1 -1
  204. package/dist/core/workflow/run-state.js +8 -1
  205. package/dist/core/workflow/types.d.ts +33 -1
  206. package/dist/core/workflow/types.d.ts.map +1 -1
  207. package/dist/core/workflow/types.js +2 -1
  208. package/dist/lib/browser/client.d.ts +5 -0
  209. package/dist/lib/browser/client.d.ts.map +1 -1
  210. package/dist/lib/browser/client.js +1 -0
  211. package/dist/lib/browser/index.d.ts +2 -0
  212. package/dist/lib/browser/index.d.ts.map +1 -1
  213. package/dist/lib/browser/index.js +2 -0
  214. package/dist/lib/browser/qa-run-contracts.d.ts +173 -0
  215. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  216. package/dist/lib/browser/qa-run-contracts.js +226 -0
  217. package/dist/lib/browser/qa-run.d.ts +51 -0
  218. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  219. package/dist/lib/browser/qa-run.js +684 -0
  220. package/dist/lib/coord-root-id.d.ts +9 -0
  221. package/dist/lib/coord-root-id.d.ts.map +1 -0
  222. package/dist/lib/coord-root-id.js +17 -0
  223. package/dist/lib/instructions/apply.d.ts +3 -1
  224. package/dist/lib/instructions/apply.d.ts.map +1 -1
  225. package/dist/lib/instructions/apply.js +74 -3
  226. package/dist/lib/instructions/templates.d.ts.map +1 -1
  227. package/dist/lib/instructions/templates.js +6 -0
  228. package/package.json +12 -1
  229. package/src/commander.ts +26 -0
  230. package/src/commands/agents.ts +102 -49
  231. package/src/commands/artifacts.ts +40 -2
  232. package/src/commands/browse.ts +15 -0
  233. package/src/commands/diagnostics.ts +271 -0
  234. package/src/commands/files.ts +102 -2
  235. package/src/commands/governor.ts +8 -0
  236. package/src/commands/ledger-v3.ts +82 -0
  237. package/src/commands/qa-run.ts +184 -0
  238. package/src/commands/resources.ts +38 -0
  239. package/src/commands/supervisor.ts +141 -0
  240. package/src/commands/web.ts +19 -0
  241. package/src/commands/workflow.ts +8 -0
  242. package/src/core/agents/cli.ts +7 -3
  243. package/src/core/agents/health.ts +142 -0
  244. package/src/core/agents/mailbox.ts +373 -0
  245. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  246. package/src/core/agents/render/prompt-context.ts +42 -12
  247. package/src/core/agents/render/session-context.ts +37 -3
  248. package/src/core/agents/rules/commit-conflict.ts +58 -1
  249. package/src/core/agents/rules/stop-hook.ts +6 -12
  250. package/src/core/agents/session-finalizer-v3.ts +4 -1
  251. package/src/core/artifacts/index.ts +196 -10
  252. package/src/core/config.ts +111 -2
  253. package/src/core/diagnostics/advice.ts +147 -0
  254. package/src/core/diagnostics/bundle.ts +1142 -0
  255. package/src/core/diagnostics/comparison.ts +565 -0
  256. package/src/core/diagnostics/contract.ts +311 -0
  257. package/src/core/diagnostics/identity.ts +11 -0
  258. package/src/core/diagnostics/index.ts +7 -0
  259. package/src/core/diagnostics/replay.ts +314 -0
  260. package/src/core/diagnostics/sanitize.ts +97 -0
  261. package/src/core/events/legacy-storage/compression.ts +130 -0
  262. package/src/core/events/legacy-storage/index.ts +1 -0
  263. package/src/core/events/v3/archive-retention.ts +251 -0
  264. package/src/core/events/v3/capabilities.ts +2 -0
  265. package/src/core/events/v3/index.ts +9 -0
  266. package/src/core/events/v3/live-routing.ts +1 -0
  267. package/src/core/events/v3/producers/hook-base.ts +14 -2
  268. package/src/core/events/v3/producers/intake.ts +1 -0
  269. package/src/core/events/v3/producers/recorder.ts +110 -1
  270. package/src/core/hooks/adapter/output.ts +12 -1
  271. package/src/core/hooks/cli.ts +192 -215
  272. package/src/core/hooks/health.ts +146 -0
  273. package/src/core/resources/contract.ts +98 -0
  274. package/src/core/resources/index.ts +5 -0
  275. package/src/core/resources/sampler.ts +458 -0
  276. package/src/core/resources/service-status.ts +62 -0
  277. package/src/core/resources/service.ts +337 -0
  278. package/src/core/resources/storage.ts +44 -0
  279. package/src/core/storage/atomic-json.ts +15 -0
  280. package/src/core/storage/builtins.ts +62 -6
  281. package/src/core/storage/logger.ts +2 -0
  282. package/src/core/storage/query.ts +58 -0
  283. package/src/core/supervisor/activity.ts +193 -0
  284. package/src/core/supervisor/contract.ts +312 -0
  285. package/src/core/supervisor/explanations.ts +104 -0
  286. package/src/core/supervisor/findings.ts +788 -0
  287. package/src/core/supervisor/history.ts +61 -0
  288. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  289. package/src/core/supervisor/hook-health-storage.ts +25 -0
  290. package/src/core/supervisor/hook-health.ts +301 -0
  291. package/src/core/supervisor/hooks.ts +35 -0
  292. package/src/core/supervisor/index.ts +14 -0
  293. package/src/core/supervisor/log-feed.ts +131 -0
  294. package/src/core/supervisor/service.ts +511 -0
  295. package/src/core/supervisor/services.ts +203 -0
  296. package/src/core/supervisor/status.ts +59 -0
  297. package/src/core/supervisor/storage.ts +126 -0
  298. package/src/core/supervisor/timeline.ts +133 -0
  299. package/src/core/workflow/admission.ts +215 -0
  300. package/src/core/workflow/engine.ts +93 -1
  301. package/src/core/workflow/index.ts +10 -0
  302. package/src/core/workflow/proof.ts +54 -1
  303. package/src/core/workflow/run-state.ts +13 -1
  304. package/src/core/workflow/types.ts +39 -1
  305. package/src/lib/browser/client.ts +6 -0
  306. package/src/lib/browser/index.ts +29 -0
  307. package/src/lib/browser/qa-run-contracts.ts +380 -0
  308. package/src/lib/browser/qa-run.ts +784 -0
  309. package/src/lib/coord-root-id.ts +23 -0
  310. package/src/lib/instructions/apply.ts +81 -2
  311. package/src/lib/instructions/templates.ts +6 -0
@@ -0,0 +1,62 @@
1
+ import { hostname } from "node:os";
2
+ import { checkPidToken } from "../agents/state/proc-start.ts";
3
+ import {
4
+ RESOURCE_SERVICE_STATUS_SCHEMA_VERSION,
5
+ type ResourceServiceStatus,
6
+ type ResourceServiceStatusRecord,
7
+ } from "./contract.ts";
8
+ import { readResourceServiceRecord, resourcePaths } from "./storage.ts";
9
+
10
+ const STALE_HEARTBEAT_MS = 15_000;
11
+
12
+ export function readResourceServiceStatus(
13
+ coordRoot: string,
14
+ nowMs = Date.now(),
15
+ ): ResourceServiceStatus {
16
+ const paths = resourcePaths(coordRoot);
17
+ const record = readResourceServiceRecord(coordRoot);
18
+ if (!validRecord(record)) {
19
+ return {
20
+ running: false,
21
+ stale: false,
22
+ status_path: paths.service,
23
+ snapshot_path: paths.snapshot,
24
+ };
25
+ }
26
+ const heartbeatAt = Date.parse(record.heartbeat_at);
27
+ const stale = !Number.isFinite(heartbeatAt) || nowMs - heartbeatAt > STALE_HEARTBEAT_MS;
28
+ const local = record.host === hostname();
29
+ const running =
30
+ local &&
31
+ !stale &&
32
+ (record.state === "starting" || record.state === "running" || record.state === "stopping") &&
33
+ pidStillMatches(record.pid, record.start_token);
34
+ return {
35
+ running,
36
+ stale,
37
+ record,
38
+ status_path: paths.service,
39
+ snapshot_path: paths.snapshot,
40
+ };
41
+ }
42
+
43
+ function validRecord(
44
+ value: ResourceServiceStatusRecord | undefined,
45
+ ): value is ResourceServiceStatusRecord {
46
+ return (
47
+ value?.schema_version === RESOURCE_SERVICE_STATUS_SCHEMA_VERSION &&
48
+ Number.isInteger(value.pid) &&
49
+ value.pid > 0 &&
50
+ typeof value.host === "string" &&
51
+ typeof value.heartbeat_at === "string"
52
+ );
53
+ }
54
+
55
+ function pidStillMatches(pid: number, startToken: string | undefined): boolean {
56
+ try {
57
+ process.kill(pid, 0);
58
+ } catch (error) {
59
+ if ((error as NodeJS.ErrnoException).code !== "EPERM") return false;
60
+ }
61
+ return checkPidToken(pid, startToken) !== "mismatch";
62
+ }
@@ -0,0 +1,337 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import {
4
+ closeSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ openSync,
8
+ readFileSync,
9
+ rmSync,
10
+ unlinkSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { hostname, platform } from "node:os";
14
+ import { resolve } from "node:path";
15
+ import { checkPidToken, processStartToken } from "../agents/state/proc-start.ts";
16
+ import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
17
+ import {
18
+ RESOURCE_SERVICE_STATUS_SCHEMA_VERSION,
19
+ type ResourceSamplerState,
20
+ type ResourceServiceStatus,
21
+ type ResourceServiceStatusRecord,
22
+ } from "./contract.ts";
23
+ import { sampleResources } from "./sampler.ts";
24
+ import { readResourceServiceStatus } from "./service-status.ts";
25
+ import { resourcePaths, writePrivateJsonAtomic } from "./storage.ts";
26
+
27
+ export const RESOURCE_SERVICE_DEFAULT_INTERVAL_MS = 2_000;
28
+ const RESOURCE_SERVICE_MIN_INTERVAL_MS = 500;
29
+ const RESOURCE_SERVICE_MAX_INTERVAL_MS = 60_000;
30
+
31
+ interface ResourceServiceLease {
32
+ pid: number;
33
+ start_token?: string;
34
+ host: string;
35
+ nonce: string;
36
+ created_at: string;
37
+ }
38
+
39
+ export interface RunResourceServiceInput {
40
+ coordRoot: string;
41
+ intervalMs?: number;
42
+ maxSamples?: number;
43
+ now?: () => Date;
44
+ sample?: typeof sampleResources;
45
+ wait?: (milliseconds: number) => Promise<void>;
46
+ }
47
+
48
+ export type EnsureResourceServiceResult =
49
+ | { state: "running" | "started"; status: ResourceServiceStatus }
50
+ | { state: "unsupported" | "unavailable"; status: ResourceServiceStatus; error: string };
51
+
52
+ export async function spawnResourceService(
53
+ coordRootRaw: string,
54
+ options: { intervalMs?: number } = {},
55
+ ): Promise<ResourceServiceStatus> {
56
+ const coordRoot = resolve(coordRootRaw);
57
+ const current = readResourceServiceStatus(coordRoot);
58
+ if (current.running) {
59
+ throw new Error(`resource observer is already running under pid ${current.record?.pid}`);
60
+ }
61
+ const intervalMs = normalizeInterval(options.intervalMs);
62
+ const paths = resourcePaths(coordRoot);
63
+ mkdirSync(paths.root, { recursive: true, mode: 0o700 });
64
+ rmSync(paths.stop, { force: true });
65
+ const harnBin = new URL("../../../bin/harn", import.meta.url).pathname;
66
+ if (!existsSync(harnBin)) throw new Error(`cannot find harn executable at ${harnBin}`);
67
+ let spawnError: Error | undefined;
68
+ const child = spawn(
69
+ harnBin,
70
+ ["resources", "service", "daemon", "--root", coordRoot, "--interval-ms", String(intervalMs)],
71
+ {
72
+ cwd: coordRoot,
73
+ detached: true,
74
+ stdio: ["ignore", "ignore", "ignore"],
75
+ env: {
76
+ ...process.env,
77
+ HARNERY_COORD_ROOT_OVERRIDE: coordRoot,
78
+ HARNERY_OUTPUT_SESSION_TEE: "0",
79
+ },
80
+ },
81
+ );
82
+ child.once("error", (error) => {
83
+ spawnError = error;
84
+ });
85
+ child.unref();
86
+ const deadline = Date.now() + 5_000;
87
+ while (Date.now() < deadline) {
88
+ await delay(50);
89
+ if (spawnError) throw spawnError;
90
+ const status = readResourceServiceStatus(coordRoot);
91
+ if (status.running && status.record?.pid === child.pid) return status;
92
+ if (child.exitCode !== null) break;
93
+ }
94
+ throw new Error(`resource observer failed to start; inspect ${paths.service}`);
95
+ }
96
+
97
+ export async function ensureResourceServiceRunning(
98
+ coordRoot: string,
99
+ ): Promise<EnsureResourceServiceResult> {
100
+ const status = readResourceServiceStatus(coordRoot);
101
+ if (status.running) return { state: "running", status };
102
+ if (platform() !== "linux") {
103
+ return {
104
+ state: "unsupported",
105
+ status,
106
+ error: `resource observer does not yet support ${platform()}`,
107
+ };
108
+ }
109
+ try {
110
+ const started = await spawnResourceService(coordRoot);
111
+ return { state: "started", status: started };
112
+ } catch (error) {
113
+ return {
114
+ state: "unavailable",
115
+ status: readResourceServiceStatus(coordRoot),
116
+ error: error instanceof Error ? error.message : String(error),
117
+ };
118
+ }
119
+ }
120
+
121
+ export function requestResourceServiceStop(coordRootRaw: string): ResourceServiceStatus {
122
+ const coordRoot = resolve(coordRootRaw);
123
+ const paths = resourcePaths(coordRoot);
124
+ const status = readResourceServiceStatus(coordRoot);
125
+ mkdirSync(paths.root, { recursive: true, mode: 0o700 });
126
+ writePrivateJsonAtomic(paths.stop, {
127
+ requested_at: new Date().toISOString(),
128
+ requested_by_pid: process.pid,
129
+ });
130
+ if (status.running && status.record?.host === hostname()) {
131
+ try {
132
+ process.kill(status.record.pid, "SIGTERM");
133
+ } catch {
134
+ // The durable stop file handles a racing exit.
135
+ }
136
+ }
137
+ return readResourceServiceStatus(coordRoot);
138
+ }
139
+
140
+ export async function runResourceService(
141
+ input: RunResourceServiceInput,
142
+ ): Promise<ResourceServiceStatusRecord> {
143
+ const coordRoot = resolve(input.coordRoot);
144
+ const paths = resourcePaths(coordRoot);
145
+ const intervalMs = normalizeInterval(input.intervalMs);
146
+ const now = input.now ?? (() => new Date());
147
+ const sample = input.sample ?? sampleResources;
148
+ const wait = input.wait ?? delay;
149
+ const lease = acquireLease(coordRoot, now());
150
+ rmSync(paths.stop, { force: true });
151
+ const startedAt = now().toISOString();
152
+ const daemonStartToken = processStartToken(process.pid);
153
+ const status: ResourceServiceStatusRecord = {
154
+ schema_version: RESOURCE_SERVICE_STATUS_SCHEMA_VERSION,
155
+ pid: process.pid,
156
+ ...(daemonStartToken ? { start_token: daemonStartToken } : {}),
157
+ host: hostname(),
158
+ nonce: lease.nonce,
159
+ state: "starting",
160
+ started_at: startedAt,
161
+ heartbeat_at: startedAt,
162
+ interval_ms: intervalMs,
163
+ sample_count: 0,
164
+ };
165
+ let stopping = false;
166
+ let previous: ResourceSamplerState | undefined;
167
+ const writeStatus = () => {
168
+ status.heartbeat_at = now().toISOString();
169
+ writePrivateJsonAtomic(paths.service, status);
170
+ };
171
+ const requestStop = () => {
172
+ stopping = true;
173
+ status.state = "stopping";
174
+ writeStatus();
175
+ };
176
+ process.on("SIGINT", requestStop);
177
+ process.on("SIGTERM", requestStop);
178
+ status.state = "running";
179
+ writeStatus();
180
+ log(coordRoot, "resource_observer.started", { pid: process.pid, interval_ms: intervalMs });
181
+ try {
182
+ while (!stopping && !existsSync(paths.stop)) {
183
+ const cycleStarted = Date.now();
184
+ try {
185
+ const result = sample(coordRoot, previous);
186
+ previous = result.state;
187
+ writePrivateJsonAtomic(paths.snapshot, result.snapshot);
188
+ status.sample_count += 1;
189
+ status.last_sample_at = result.snapshot.sampled_at;
190
+ status.last_error_code = undefined;
191
+ log(coordRoot, "resource_observer.sample", {
192
+ sample_count: status.sample_count,
193
+ cpu_percent: result.snapshot.machine.cpu_percent ?? -1,
194
+ memory_percent: result.snapshot.machine.memory_percent ?? -1,
195
+ visible_processes: result.snapshot.visible_process_count,
196
+ unattributed_processes: result.snapshot.unattributed_process_count,
197
+ duration_ms: result.snapshot.sample_duration_ms,
198
+ collector_cpu_ms: result.snapshot.collector_cpu_ms,
199
+ });
200
+ } catch (error) {
201
+ status.last_error_code = resourceErrorCode(error);
202
+ status.state = "error";
203
+ log(
204
+ coordRoot,
205
+ "resource_observer.sample_error",
206
+ { reason_code: status.last_error_code },
207
+ error,
208
+ );
209
+ }
210
+ if (status.state === "error") status.state = "running";
211
+ writeStatus();
212
+ if (input.maxSamples !== undefined && status.sample_count >= input.maxSamples) break;
213
+ const remaining = Math.max(0, intervalMs - (Date.now() - cycleStarted));
214
+ if (!stopping && !existsSync(paths.stop)) await wait(remaining);
215
+ }
216
+ } finally {
217
+ process.off("SIGINT", requestStop);
218
+ process.off("SIGTERM", requestStop);
219
+ status.state = "stopped";
220
+ status.stopped_at = now().toISOString();
221
+ writeStatus();
222
+ log(coordRoot, "resource_observer.stopped", { sample_count: status.sample_count });
223
+ try {
224
+ await closeProcessLoggers();
225
+ } finally {
226
+ releaseLease(paths.lease, lease.nonce);
227
+ }
228
+ }
229
+ return status;
230
+ }
231
+
232
+ function acquireLease(coordRoot: string, now: Date): ResourceServiceLease {
233
+ const paths = resourcePaths(coordRoot);
234
+ mkdirSync(paths.root, { recursive: true, mode: 0o700 });
235
+ for (let attempt = 0; attempt < 2; attempt++) {
236
+ const daemonStartToken = processStartToken(process.pid);
237
+ const lease: ResourceServiceLease = {
238
+ pid: process.pid,
239
+ ...(daemonStartToken ? { start_token: daemonStartToken } : {}),
240
+ host: hostname(),
241
+ nonce: randomUUID(),
242
+ created_at: now.toISOString(),
243
+ };
244
+ try {
245
+ const fd = openSync(paths.lease, "wx", 0o600);
246
+ try {
247
+ writeFileSync(fd, `${JSON.stringify(lease)}\n`, "utf8");
248
+ } finally {
249
+ closeSync(fd);
250
+ }
251
+ return lease;
252
+ } catch (error) {
253
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
254
+ const existing = readLease(paths.lease);
255
+ if (existing && leaseIsLive(existing)) {
256
+ throw new Error(`resource observer lease is held by pid ${existing.pid}`);
257
+ }
258
+ try {
259
+ unlinkSync(paths.lease);
260
+ } catch {
261
+ // A concurrent starter may have replaced or removed the stale lease.
262
+ }
263
+ }
264
+ }
265
+ throw new Error("resource observer could not acquire its singleton lease");
266
+ }
267
+
268
+ function releaseLease(path: string, nonce: string): void {
269
+ const current = readLease(path);
270
+ if (current?.nonce !== nonce) return;
271
+ try {
272
+ unlinkSync(path);
273
+ } catch {
274
+ // A missing lease is already released.
275
+ }
276
+ }
277
+
278
+ function readLease(path: string): ResourceServiceLease | undefined {
279
+ try {
280
+ const value = JSON.parse(readFileSync(path, "utf8")) as ResourceServiceLease;
281
+ return value && Number.isInteger(value.pid) && typeof value.nonce === "string"
282
+ ? value
283
+ : undefined;
284
+ } catch {
285
+ return undefined;
286
+ }
287
+ }
288
+
289
+ function leaseIsLive(lease: ResourceServiceLease): boolean {
290
+ if (lease.host !== hostname()) return true;
291
+ try {
292
+ process.kill(lease.pid, 0);
293
+ } catch (error) {
294
+ if ((error as NodeJS.ErrnoException).code !== "EPERM") return false;
295
+ }
296
+ return checkPidToken(lease.pid, lease.start_token) !== "mismatch";
297
+ }
298
+
299
+ function log(
300
+ coordRoot: string,
301
+ event: string,
302
+ fields: Record<string, unknown>,
303
+ error?: unknown,
304
+ ): void {
305
+ try {
306
+ const logger = processLogger(coordRoot, "resource-observer");
307
+ if (error) logger.error(event, legacyLogFields(fields), error);
308
+ else logger.info(event, legacyLogFields(fields));
309
+ } catch {
310
+ // Observability must never break the observer loop.
311
+ }
312
+ }
313
+
314
+ function resourceErrorCode(error: unknown): string {
315
+ const message = error instanceof Error ? error.message : String(error);
316
+ if (/procfs|\/proc\//i.test(message)) return "procfs_read_failed";
317
+ if (/permission|EACCES|EPERM/i.test(message)) return "permission_denied";
318
+ return "sample_failed";
319
+ }
320
+
321
+ function normalizeInterval(value: number | undefined): number {
322
+ const interval = value ?? RESOURCE_SERVICE_DEFAULT_INTERVAL_MS;
323
+ if (
324
+ !Number.isFinite(interval) ||
325
+ interval < RESOURCE_SERVICE_MIN_INTERVAL_MS ||
326
+ interval > RESOURCE_SERVICE_MAX_INTERVAL_MS
327
+ ) {
328
+ throw new Error(
329
+ `resource observer interval must be ${RESOURCE_SERVICE_MIN_INTERVAL_MS}-${RESOURCE_SERVICE_MAX_INTERVAL_MS} ms`,
330
+ );
331
+ }
332
+ return Math.round(interval);
333
+ }
334
+
335
+ function delay(milliseconds: number): Promise<void> {
336
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
337
+ }
@@ -0,0 +1,44 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import type { ResourceServiceStatusRecord, ResourceSnapshot } from "./contract.ts";
4
+
5
+ export { writePrivateJsonAtomic } from "../storage/atomic-json.ts";
6
+
7
+ export interface ResourcePaths {
8
+ root: string;
9
+ service: string;
10
+ lease: string;
11
+ stop: string;
12
+ snapshot: string;
13
+ }
14
+
15
+ export function resourcePaths(coordRootRaw: string): ResourcePaths {
16
+ const root = join(resolve(coordRootRaw), ".harnery", "resources");
17
+ return {
18
+ root,
19
+ service: join(root, "service.json"),
20
+ lease: join(root, "service.lease.json"),
21
+ stop: join(root, "stop.json"),
22
+ snapshot: join(root, "snapshot.json"),
23
+ };
24
+ }
25
+
26
+ export function readResourceSnapshot(coordRoot: string): ResourceSnapshot | undefined {
27
+ return readJson<ResourceSnapshot>(resourcePaths(coordRoot).snapshot);
28
+ }
29
+
30
+ export function readResourceServiceRecord(
31
+ coordRoot: string,
32
+ ): ResourceServiceStatusRecord | undefined {
33
+ return readJson<ResourceServiceStatusRecord>(resourcePaths(coordRoot).service);
34
+ }
35
+
36
+ function readJson<T>(path: string): T | undefined {
37
+ if (!existsSync(path)) return undefined;
38
+ try {
39
+ const value = JSON.parse(readFileSync(path, "utf8")) as unknown;
40
+ return value && typeof value === "object" ? (value as T) : undefined;
41
+ } catch {
42
+ return undefined;
43
+ }
44
+ }
@@ -0,0 +1,15 @@
1
+ import { chmodSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+
4
+ /** Write one private JSON projection without exposing a partial frame to readers. */
5
+ export function writePrivateJsonAtomic(path: string, value: unknown): void {
6
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
7
+ const temporary = `${path}.tmp.${process.pid}.${Date.now()}`;
8
+ writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600 });
9
+ try {
10
+ chmodSync(temporary, 0o600);
11
+ } catch {
12
+ // Windows does not expose POSIX modes. The atomic write still applies.
13
+ }
14
+ renameSync(temporary, path);
15
+ }
@@ -35,6 +35,8 @@ const LOG_PARTITIONS = [
35
35
  "agent-operational",
36
36
  "web-performance",
37
37
  "semantic-service",
38
+ "resource-observer",
39
+ "supervisor",
38
40
  "governor-service",
39
41
  "presence-relay",
40
42
  "tunnel-process",
@@ -59,8 +61,8 @@ const V3_ARCHIVE_PROVIDER: HarneryStorageProvider = {
59
61
  provider_id: "event-v3-archive-provider",
60
62
  kind: "filesystem",
61
63
  inventory: "filesystem",
62
- maintenance: "none",
63
- lifecycle_authority: "Event Ledger V3 logical authority reader",
64
+ maintenance: "delegated",
65
+ lifecycle_authority: "Event Ledger V3 closed-epoch retention policy",
64
66
  partitions: ["canonical", "support"],
65
67
  };
66
68
 
@@ -81,7 +83,7 @@ function eventFamilies(): HarneryStorageFamily[] {
81
83
  family({
82
84
  id: "event-v3-canonical-active",
83
85
  owner: "Event Ledger V3 authority",
84
- storage_class: "canonical-authority",
86
+ storage_class: "durable-object-history",
85
87
  roots: (context) => [
86
88
  exact(context, ".harnery/ledgers/v3/active.ndjson", "file"),
87
89
  exact(context, ".harnery/ledgers/v3/catalog.json", "file"),
@@ -112,14 +114,14 @@ function eventFamilies(): HarneryStorageFamily[] {
112
114
  format: "canonical-ndjson",
113
115
  durability: "immutable",
114
116
  writer_model: "object-owned",
115
- policy: authorityPolicy("event-v3-archive-canonical-v1"),
117
+ policy: closedEpochPolicy("event-v3-archive-canonical-v2"),
116
118
  consumers: ["Event Ledger V3 logical authority reader", "recovery verifier"],
117
119
  provider: V3_ARCHIVE_PROVIDER,
118
120
  }),
119
121
  family({
120
122
  id: "event-v3-support-active",
121
123
  owner: "Event Ledger V3 producer recovery",
122
- storage_class: "recovery-state",
124
+ storage_class: "durable-object-history",
123
125
  roots: (context) => [
124
126
  subtree(context, ".harnery/ledgers/v3/diagnostics", OWNER_PROTOCOL_LINKS),
125
127
  subtree(context, ".harnery/ledgers/v3/diagnostic-summaries", OWNER_PROTOCOL_LINKS),
@@ -167,7 +169,7 @@ function eventFamilies(): HarneryStorageFamily[] {
167
169
  format: "files",
168
170
  durability: "immutable",
169
171
  writer_model: "object-owned",
170
- policy: recoveryPolicy("event-v3-archive-support-v1"),
172
+ policy: closedEpochPolicy("event-v3-archive-support-v2"),
171
173
  consumers: ["Event Ledger V3 support reader", "recovery verifier", "agents health"],
172
174
  provider: V3_ARCHIVE_PROVIDER,
173
175
  }),
@@ -304,6 +306,14 @@ function logFamilies(): HarneryStorageFamily[] {
304
306
  ),
305
307
  (context) => [exact(context, ".harnery/semantic/v2/service.log", "file")],
306
308
  ),
309
+ operationalFamily(
310
+ "resource-observer-log",
311
+ "resource observer",
312
+ "resource-observer",
313
+ 14,
314
+ 64 * MIB,
315
+ ),
316
+ operationalFamily("supervisor-log", "local supervisor", "supervisor", 14, 64 * MIB),
307
317
  withCurrentRoots(
308
318
  operationalFamily(
309
319
  "governor-service-log",
@@ -463,6 +473,39 @@ function cacheFamilies(): HarneryStorageFamily[] {
463
473
  "canonical events and source documents",
464
474
  ),
465
475
  }),
476
+ family({
477
+ id: "resource-observer-cache",
478
+ owner: "resource observer",
479
+ storage_class: "repairable-cache",
480
+ roots: (context) => [subtree(context, ".harnery/resources")],
481
+ format: "json",
482
+ durability: "reconstructable",
483
+ writer_model: "object-owned",
484
+ policy: cachePolicy("resource-observer-cache-v1", "local process and machine samples"),
485
+ consumers: ["resource observer", "dashboard", "storage inventory", "storage health"],
486
+ provider: filesystemProvider(
487
+ "resource-observer-cache-provider",
488
+ "local process and machine samples",
489
+ ),
490
+ }),
491
+ family({
492
+ id: "supervisor-cache",
493
+ owner: "local supervisor",
494
+ storage_class: "repairable-cache",
495
+ roots: (context) => [subtree(context, ".harnery/supervisor")],
496
+ format: "json",
497
+ durability: "reconstructable",
498
+ writer_model: "object-owned",
499
+ policy: cachePolicy(
500
+ "supervisor-cache-v1",
501
+ "local resources, service health, log cursors, and finding projections",
502
+ ),
503
+ consumers: ["local supervisor", "dashboard", "storage inventory", "storage health"],
504
+ provider: filesystemProvider(
505
+ "supervisor-cache-provider",
506
+ "bounded local diagnostic observations",
507
+ ),
508
+ }),
466
509
  family({
467
510
  id: "structured-log-metrics",
468
511
  owner: "structured log provider",
@@ -770,6 +813,19 @@ function ownerHistoryPolicy(version: string): HarneryStoragePolicy {
770
813
  });
771
814
  }
772
815
 
816
+ function closedEpochPolicy(version: string): HarneryStoragePolicy {
817
+ return policy(version, "active", "private", "owner-content", {
818
+ status: "active",
819
+ mode: "delegated",
820
+ maxAge: bounded(7 * DAY_MS, "milliseconds"),
821
+ maxBytes: bounded(1024 * MIB, "bytes"),
822
+ maxFiles: unbounded("files", "complete epoch directories are the deletion unit"),
823
+ maxRecords: unbounded("records", "complete epoch directories are the deletion unit"),
824
+ reason:
825
+ "The Event Ledger owner keeps the newest two complete epochs and revalidates each closed epoch before removal.",
826
+ });
827
+ }
828
+
773
829
  function delegatedHistoryPolicy(version: string): HarneryStoragePolicy {
774
830
  const result = ownerHistoryPolicy(version);
775
831
  result.retention.mode = "delegated";
@@ -517,6 +517,8 @@ const PROCESS_LOGGER_BINDINGS = {
517
517
  "agent-hook": "agent-hook-debug-log",
518
518
  "agent-coord": "agent-coord-debug-log",
519
519
  "semantic-service": "semantic-service-log",
520
+ "resource-observer": "resource-observer-log",
521
+ supervisor: "supervisor-log",
520
522
  "governor-service": "governor-service-log",
521
523
  "presence-relay": "presence-relay-log",
522
524
  } as const;
@@ -42,6 +42,12 @@ export interface HarneryLogFollowCursor {
42
42
  active_offset: number;
43
43
  }
44
44
 
45
+ export interface HarneryRecentActiveLogResult {
46
+ records: readonly HarneryLogRecordV1[];
47
+ bytes_examined: number;
48
+ truncated: boolean;
49
+ }
50
+
45
51
  const LEVELS: readonly HarneryLogLevel[] = ["trace", "debug", "info", "warn", "error", "fatal"];
46
52
  const MAX_UNBOUNDED_RECORD_BYTES = 1024 * 1024;
47
53
  const STREAM_CHUNK_BYTES = 64 * 1024;
@@ -109,6 +115,58 @@ export function rotationFollowCursor(
109
115
  };
110
116
  }
111
117
 
118
+ /**
119
+ * Read the newest complete records from one family's active segment without
120
+ * walking sealed history. Dashboard-style live views need a cheap bounded
121
+ * snapshot; the full `queryLogs` surface remains the historical query path.
122
+ */
123
+ export function readRecentActiveLogs(
124
+ family: HarneryRegisteredStorageFamily,
125
+ options: { max_records: number; max_bytes: number },
126
+ ): HarneryRecentActiveLogResult {
127
+ if (
128
+ !Number.isSafeInteger(options.max_records) ||
129
+ options.max_records <= 0 ||
130
+ !Number.isSafeInteger(options.max_bytes) ||
131
+ options.max_bytes <= 0
132
+ ) {
133
+ throw new Error("recent log read requires positive budgets");
134
+ }
135
+ const active = join(familyLogDirectory(family), "active.jsonl");
136
+ if (!existsSync(active)) return { records: [], bytes_examined: 0, truncated: false };
137
+
138
+ const opened = openRegularNoFollow(active);
139
+ try {
140
+ const size = fstatSync(opened.fd).size;
141
+ const start = Math.max(0, size - options.max_bytes);
142
+ const length = size - start;
143
+ if (length === 0) return { records: [], bytes_examined: 0, truncated: false };
144
+ const buffer = Buffer.allocUnsafe(length);
145
+ let consumed = 0;
146
+ while (consumed < length) {
147
+ const count = readSync(opened.fd, buffer, consumed, length - consumed, start + consumed);
148
+ if (count === 0) break;
149
+ consumed += count;
150
+ }
151
+ assertOpenFileIdentity(active, opened.fd, opened.parent);
152
+ const text = buffer.subarray(0, consumed).toString("utf8");
153
+ let lines = text.split("\n");
154
+ // A bounded tail can begin in the middle of a record. The active writer
155
+ // can also leave a not-yet-complete final record while this snapshot reads.
156
+ if (start > 0) lines = lines.slice(1);
157
+ if (!text.endsWith("\n")) lines = lines.slice(0, -1);
158
+ const records = lines.filter((line) => line.trim()).map(parseLogRecord);
159
+ const truncated = start > 0 || records.length > options.max_records;
160
+ return {
161
+ records: records.slice(-options.max_records),
162
+ bytes_examined: consumed,
163
+ truncated,
164
+ };
165
+ } finally {
166
+ closeSync(opened.fd);
167
+ }
168
+ }
169
+
112
170
  export async function readLogFollow(
113
171
  family: HarneryRegisteredStorageFamily,
114
172
  cursor: HarneryLogFollowCursor,