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,97 @@
1
+ const SENSITIVE_KEY =
2
+ /(?:authorization|cookie|credential|password|passwd|private[_-]?key|secret|session[_-]?token|access[_-]?token|refresh[_-]?token|api[_-]?key)/i;
3
+ const OMITTED_CONTENT_KEY =
4
+ /(?:^|[_-])(?:environment|env|prompt|prompts|tool[_-]?payload|tool[_-]?result|transcript|transcripts)(?:$|[_-])/i;
5
+ const HOME_PATH = /(?:[A-Za-z]:\\Users\\[^\\\s]+|\/home\/[^/\s]+|\/Users\/[^/\s]+)/g;
6
+ const BEARER_VALUE = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
7
+ const URL_CREDENTIALS = /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi;
8
+ const PRIVATE_KEY = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
9
+ const TOKENISH_VALUE = /\b(?:sk|pk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{16,}\b/g;
10
+ const AWS_ACCESS_KEY = /\bAKIA[A-Z0-9]{16,}\b/g;
11
+ const SENSITIVE_QUERY = /([?&](?:access_token|api_key|key|password|secret|token)=)[^&#\s]+/gi;
12
+ const SENSITIVE_ENV = /(\b[A-Z0-9_]*(?:API_KEY|PASSWORD|SECRET|TOKEN)=)[^\s]+/g;
13
+ const SENSITIVE_ARGUMENT = /(\B--?(?:access-token|api-key|password|secret|token)\s+)[^\s]+/gi;
14
+
15
+ export interface SanitizationStats {
16
+ sanitized_value_count: number;
17
+ omitted_value_count: number;
18
+ }
19
+
20
+ export function sanitizeDiagnosticValue(value: unknown): {
21
+ value: unknown;
22
+ stats: SanitizationStats;
23
+ } {
24
+ const stats: SanitizationStats = { sanitized_value_count: 0, omitted_value_count: 0 };
25
+ return { value: sanitize(value, stats, 0), stats };
26
+ }
27
+
28
+ function sanitize(value: unknown, stats: SanitizationStats, depth: number): unknown {
29
+ if (depth > 24) {
30
+ stats.omitted_value_count += 1;
31
+ return "[OMITTED:depth-limit]";
32
+ }
33
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
34
+ if (typeof value === "string") return sanitizeString(value, stats);
35
+ if (Array.isArray(value)) {
36
+ const sliced = value.slice(0, 5_000);
37
+ const kept = sliced.map((item, index) => {
38
+ if (
39
+ typeof sliced[index - 1] === "string" &&
40
+ /^(?:--?)(?:access-token|api-key|password|secret|token)$/i.test(sliced[index - 1] as string)
41
+ ) {
42
+ stats.sanitized_value_count += 1;
43
+ return "[REDACTED:argument]";
44
+ }
45
+ return sanitize(item, stats, depth + 1);
46
+ });
47
+ if (kept.length !== value.length) stats.omitted_value_count += value.length - kept.length;
48
+ return kept;
49
+ }
50
+ if (!value || typeof value !== "object") return String(value);
51
+
52
+ const result: Record<string, unknown> = {};
53
+ const entries = Object.entries(value);
54
+ const namedSensitiveField = entries.some(
55
+ ([key, item]) => key === "name" && typeof item === "string" && SENSITIVE_KEY.test(item),
56
+ );
57
+ for (const [key, item] of entries) {
58
+ if (OMITTED_CONTENT_KEY.test(key)) {
59
+ result[key] = "[OMITTED:sensitive-content]";
60
+ stats.omitted_value_count += 1;
61
+ continue;
62
+ }
63
+ if (key === "value" && namedSensitiveField) {
64
+ result[key] = "[REDACTED:sensitive-field]";
65
+ stats.sanitized_value_count += 1;
66
+ continue;
67
+ }
68
+ if (SENSITIVE_KEY.test(key)) {
69
+ result[key] = "[REDACTED:sensitive-field]";
70
+ stats.sanitized_value_count += 1;
71
+ continue;
72
+ }
73
+ result[key] = sanitize(item, stats, depth + 1);
74
+ }
75
+ return result;
76
+ }
77
+
78
+ function sanitizeString(value: string, stats: SanitizationStats): string {
79
+ let next = value;
80
+ const replacements: Array<[RegExp, string | ((...args: string[]) => string)]> = [
81
+ [PRIVATE_KEY, "[REDACTED:private-key]"],
82
+ [BEARER_VALUE, "Bearer [REDACTED:token]"],
83
+ [TOKENISH_VALUE, "[REDACTED:token]"],
84
+ [AWS_ACCESS_KEY, "[REDACTED:access-key]"],
85
+ [SENSITIVE_QUERY, "$1[REDACTED]"],
86
+ [SENSITIVE_ENV, "$1[REDACTED]"],
87
+ [SENSITIVE_ARGUMENT, "$1[REDACTED]"],
88
+ [URL_CREDENTIALS, (_match, scheme: string) => `${scheme}[REDACTED]@`],
89
+ [HOME_PATH, "[REDACTED:home]"],
90
+ ];
91
+ for (const [pattern, replacement] of replacements) {
92
+ const replaced = next.replace(pattern, replacement as never);
93
+ if (replaced !== next) stats.sanitized_value_count += 1;
94
+ next = replaced;
95
+ }
96
+ return next;
97
+ }
@@ -0,0 +1,130 @@
1
+ /** Verified in-place compression for sealed, non-terminal legacy V1 shards. */
2
+
3
+ import { createReadStream, createWriteStream } from "node:fs";
4
+ import { chmod, lstat, rename, rm } from "node:fs/promises";
5
+ import { basename } from "node:path";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { createGzip } from "node:zlib";
8
+ import { verifyLegacyV1HardFence } from "./fence.ts";
9
+ import { inventoryLegacyV1Segments } from "./inventory.ts";
10
+ import { streamLegacyV1Rows } from "./reader.ts";
11
+
12
+ export interface LegacyV1CompressionEntry {
13
+ filename: string;
14
+ path: string;
15
+ bytes_before: number;
16
+ bytes_after: number | null;
17
+ action: "keep" | "would-compress" | "compressed";
18
+ reason: string;
19
+ }
20
+
21
+ export async function compressSealedLegacyV1Segments(
22
+ coordRoot: string,
23
+ opts: { yes?: boolean } = {},
24
+ ): Promise<LegacyV1CompressionEntry[]> {
25
+ const fence = await verifyLegacyV1HardFence(coordRoot);
26
+ const terminal = basename(fence.terminal_archive_path);
27
+ const inventory = await inventoryLegacyV1Segments(coordRoot);
28
+ const rows = inventory.map<LegacyV1CompressionEntry>((entry) => {
29
+ if (entry.filename === terminal) {
30
+ return {
31
+ filename: entry.filename,
32
+ path: entry.path,
33
+ bytes_before: entry.bytes,
34
+ bytes_after: null,
35
+ action: "keep",
36
+ reason: "terminal shard is bound by the V1 hard-fence digest",
37
+ };
38
+ }
39
+ if (entry.compressed) {
40
+ return {
41
+ filename: entry.filename,
42
+ path: entry.path,
43
+ bytes_before: entry.bytes,
44
+ bytes_after: entry.bytes,
45
+ action: "keep",
46
+ reason: "already compressed",
47
+ };
48
+ }
49
+ return {
50
+ filename: entry.filename,
51
+ path: entry.path,
52
+ bytes_before: entry.bytes,
53
+ bytes_after: null,
54
+ action: "would-compress",
55
+ reason: "sealed non-terminal V1 shard",
56
+ };
57
+ });
58
+ if (!opts.yes) return rows;
59
+
60
+ for (const row of rows) {
61
+ if (row.action !== "would-compress") continue;
62
+ const expected = inventory.find((entry) => entry.path === row.path)!;
63
+ const current = (await inventoryLegacyV1Segments(coordRoot)).find(
64
+ (entry) => entry.path === row.path,
65
+ );
66
+ if (
67
+ !current ||
68
+ current.bytes !== expected.bytes ||
69
+ current.digest !== expected.digest ||
70
+ current.compressed
71
+ ) {
72
+ throw new Error(`legacy_v1_segment_changed_before_compression:${row.filename}`);
73
+ }
74
+ const sourceStat = await lstat(row.path);
75
+ const destination = `${row.path}.gz`;
76
+ const temporary = `${row.path}.partial-${process.pid}.gz`;
77
+ try {
78
+ try {
79
+ await lstat(destination);
80
+ throw new Error(`legacy_v1_compressed_destination_exists:${row.filename}`);
81
+ } catch (error) {
82
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
83
+ }
84
+ await pipeline(
85
+ createReadStream(row.path),
86
+ createGzip({ level: 9 }),
87
+ createWriteStream(temporary, { flags: "wx", mode: 0o600 }),
88
+ );
89
+ await proveRowParity(row.path, temporary);
90
+ const unchanged = (await inventoryLegacyV1Segments(coordRoot)).find(
91
+ (entry) => entry.path === row.path,
92
+ );
93
+ if (
94
+ !unchanged ||
95
+ unchanged.bytes !== expected.bytes ||
96
+ unchanged.digest !== expected.digest
97
+ ) {
98
+ throw new Error(`legacy_v1_segment_changed_during_compression:${row.filename}`);
99
+ }
100
+ await chmod(temporary, sourceStat.mode & 0o777);
101
+ await rename(temporary, destination);
102
+ await rm(row.path);
103
+ row.action = "compressed";
104
+ row.reason = "verified gzip replacement preserved every logical row";
105
+ row.bytes_after = (await lstat(destination)).size;
106
+ row.path = destination;
107
+ row.filename = basename(destination);
108
+ } catch (error) {
109
+ await rm(temporary, { force: true });
110
+ throw error;
111
+ }
112
+ }
113
+ return rows;
114
+ }
115
+
116
+ async function proveRowParity(loose: string, compressed: string): Promise<void> {
117
+ const left = streamLegacyV1Rows(loose)[Symbol.asyncIterator]();
118
+ const right = streamLegacyV1Rows(compressed)[Symbol.asyncIterator]();
119
+ while (true) {
120
+ const [looseRow, packedRow] = await Promise.all([left.next(), right.next()]);
121
+ if (looseRow.done || packedRow.done) {
122
+ if (looseRow.done !== packedRow.done)
123
+ throw new Error("legacy_v1_compression_row_count_mismatch");
124
+ return;
125
+ }
126
+ if (!looseRow.value.bytes.equals(packedRow.value.bytes)) {
127
+ throw new Error(`legacy_v1_compression_row_mismatch:${looseRow.value.row_number}`);
128
+ }
129
+ }
130
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./canary.ts";
2
+ export * from "./compression.ts";
2
3
  export * from "./fence.ts";
3
4
  export * from "./inventory.ts";
4
5
  export * from "./manifest.ts";
@@ -0,0 +1,251 @@
1
+ /** Bounded retention for complete, closed Event Ledger V3 epochs. */
2
+
3
+ import {
4
+ existsSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { basename, join, relative, resolve } from "node:path";
13
+ import {
14
+ artifactAutoCleanIntervalHours,
15
+ type EventLedgerArchivePolicy,
16
+ eventLedgerArchivePolicy,
17
+ } from "../../config.ts";
18
+
19
+ export type EventV3ArchiveClassification =
20
+ | "retained"
21
+ | "expired"
22
+ | "over-budget"
23
+ | "protected-minimum"
24
+ | "unmanaged"
25
+ | "symlink"
26
+ | "unknown";
27
+
28
+ export interface EventV3ArchiveEntry {
29
+ name: string;
30
+ path: string;
31
+ relative_path: string;
32
+ classification: EventV3ArchiveClassification;
33
+ reason: string;
34
+ action: "keep" | "would-delete" | "deleted";
35
+ bytes: number | null;
36
+ last_modified_at: string | null;
37
+ }
38
+
39
+ export interface EventV3ArchiveAutoCleanResult {
40
+ ran: boolean;
41
+ reason: "swept" | "disabled" | "fresh" | "no-root";
42
+ deleted: number;
43
+ bytes: number;
44
+ }
45
+
46
+ const ARCHIVE_PATTERN = /^epoch-[0-9]+(?:-[0-9]+)?$/;
47
+ const AUTO_CLEAN_STAMP = ".harnery/event-v3-archive-auto-clean.json";
48
+
49
+ export function eventV3ArchivesRoot(repoRoot: string): string {
50
+ return join(resolve(repoRoot), ".harnery", "ledgers", "v3-archives");
51
+ }
52
+
53
+ export function inventoryEventV3Archives(
54
+ repoRoot: string,
55
+ opts: { now?: Date; policy?: EventLedgerArchivePolicy } = {},
56
+ ): EventV3ArchiveEntry[] {
57
+ const root = eventV3ArchivesRoot(repoRoot);
58
+ if (!existsSync(root)) return [];
59
+ const now = opts.now ?? new Date();
60
+ const policy = opts.policy ?? eventLedgerArchivePolicy(repoRoot);
61
+ const rows = readdirSync(root)
62
+ .sort()
63
+ .map((name) => classifyArchive(repoRoot, join(root, name)));
64
+ const managed = rows.filter(
65
+ (row) => row.classification === "retained" && row.bytes !== null && row.last_modified_at,
66
+ );
67
+ const newestFirst = [...managed].sort((left, right) => right.name.localeCompare(left.name));
68
+ const protectedNames = new Set(newestFirst.slice(0, policy.keepMin).map((row) => row.name));
69
+ const cutoff = now.getTime() - policy.maxAgeDays * 24 * 60 * 60 * 1000;
70
+
71
+ for (const row of managed) {
72
+ if (protectedNames.has(row.name)) {
73
+ row.classification = "protected-minimum";
74
+ row.reason = `one of the newest ${policy.keepMin} complete epochs`;
75
+ continue;
76
+ }
77
+ if (Date.parse(row.last_modified_at!) <= cutoff) {
78
+ row.classification = "expired";
79
+ row.reason = `closed epoch is older than ${policy.maxAgeDays} days`;
80
+ row.action = "would-delete";
81
+ }
82
+ }
83
+
84
+ let retainedBytes = managed.reduce((sum, row) => sum + (row.bytes ?? 0), 0);
85
+ retainedBytes -= managed.reduce(
86
+ (sum, row) => sum + (row.action === "would-delete" ? (row.bytes ?? 0) : 0),
87
+ 0,
88
+ );
89
+ const budgetCandidates = managed
90
+ .filter((row) => row.classification === "retained")
91
+ .sort((left, right) => left.name.localeCompare(right.name));
92
+ for (const row of budgetCandidates) {
93
+ if (retainedBytes <= policy.maxBytes) break;
94
+ row.classification = "over-budget";
95
+ row.reason = `closed-epoch budget is ${policy.maxBytes} bytes; oldest epochs are removed first`;
96
+ row.action = "would-delete";
97
+ retainedBytes -= row.bytes ?? 0;
98
+ }
99
+ return rows;
100
+ }
101
+
102
+ export function cleanEventV3Archives(
103
+ repoRoot: string,
104
+ opts: { yes?: boolean; now?: Date; policy?: EventLedgerArchivePolicy } = {},
105
+ ): EventV3ArchiveEntry[] {
106
+ const rows = inventoryEventV3Archives(repoRoot, opts);
107
+ if (!opts.yes) return rows;
108
+ return rows.map((entry) => {
109
+ if (entry.action !== "would-delete") return entry;
110
+ const current = inventoryEventV3Archives(repoRoot, opts).find((row) => row.path === entry.path);
111
+ if (!current) {
112
+ return { ...entry, classification: "unknown", reason: "entry disappeared", action: "keep" };
113
+ }
114
+ if (
115
+ current.action !== "would-delete" ||
116
+ current.bytes !== entry.bytes ||
117
+ current.last_modified_at !== entry.last_modified_at
118
+ ) {
119
+ return current;
120
+ }
121
+ try {
122
+ const stat = lstatSync(current.path);
123
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !ARCHIVE_PATTERN.test(current.name)) {
124
+ return {
125
+ ...current,
126
+ classification: "unknown",
127
+ reason: "entry changed before deletion",
128
+ action: "keep",
129
+ };
130
+ }
131
+ rmSync(current.path, { recursive: true, force: false });
132
+ return { ...current, action: "deleted" };
133
+ } catch (error) {
134
+ return {
135
+ ...current,
136
+ classification: "unknown",
137
+ reason: `entry changed or could not be deleted: ${error instanceof Error ? error.message : String(error)}`,
138
+ action: "keep",
139
+ };
140
+ }
141
+ });
142
+ }
143
+
144
+ export function autoCleanEventV3Archives(
145
+ repoRoot: string,
146
+ opts: { now?: Date } = {},
147
+ ): EventV3ArchiveAutoCleanResult {
148
+ const now = opts.now ?? new Date();
149
+ const root = eventV3ArchivesRoot(repoRoot);
150
+ if (!existsSync(root)) return { ran: false, reason: "no-root", deleted: 0, bytes: 0 };
151
+ const policy = eventLedgerArchivePolicy(repoRoot);
152
+ if (!policy.autoClean) return { ran: false, reason: "disabled", deleted: 0, bytes: 0 };
153
+ const stampPath = join(resolve(repoRoot), AUTO_CLEAN_STAMP);
154
+ const intervalMs = artifactAutoCleanIntervalHours() * 60 * 60 * 1000;
155
+ try {
156
+ const stamp = JSON.parse(readFileSync(stampPath, "utf8")) as { last_run_at?: string };
157
+ const last = Date.parse(stamp.last_run_at ?? "");
158
+ if (Number.isFinite(last) && now.getTime() - last < intervalMs) {
159
+ return { ran: false, reason: "fresh", deleted: 0, bytes: 0 };
160
+ }
161
+ } catch {
162
+ // Missing or unreadable stamp: sweep now.
163
+ }
164
+ mkdirSync(join(resolve(repoRoot), ".harnery"), { recursive: true });
165
+ writeFileSync(stampPath, `${JSON.stringify({ last_run_at: now.toISOString() }, null, 2)}\n`);
166
+ const rows = cleanEventV3Archives(repoRoot, { yes: true, now, policy });
167
+ const deletedRows = rows.filter((row) => row.action === "deleted");
168
+ const result = {
169
+ ran: true as const,
170
+ reason: "swept" as const,
171
+ deleted: deletedRows.length,
172
+ bytes: deletedRows.reduce((sum, row) => sum + (row.bytes ?? 0), 0),
173
+ };
174
+ writeFileSync(
175
+ stampPath,
176
+ `${JSON.stringify({ last_run_at: now.toISOString(), ...result }, null, 2)}\n`,
177
+ );
178
+ return result;
179
+ }
180
+
181
+ function classifyArchive(repoRoot: string, path: string): EventV3ArchiveEntry {
182
+ const name = basename(path);
183
+ const base: EventV3ArchiveEntry = {
184
+ name,
185
+ path,
186
+ relative_path: relative(repoRoot, path),
187
+ classification: "retained",
188
+ reason: "closed epoch is inside the retention window",
189
+ action: "keep",
190
+ bytes: null,
191
+ last_modified_at: null,
192
+ };
193
+ try {
194
+ const stat = lstatSync(path);
195
+ if (stat.isSymbolicLink()) {
196
+ return {
197
+ ...base,
198
+ classification: "symlink",
199
+ reason: "symlinks are never traversed or deleted",
200
+ bytes: stat.size,
201
+ };
202
+ }
203
+ if (!stat.isDirectory() || !ARCHIVE_PATTERN.test(name)) {
204
+ return {
205
+ ...base,
206
+ classification: "unmanaged",
207
+ reason: "entry is not a complete named epoch",
208
+ };
209
+ }
210
+ const measured = measureTree(path);
211
+ if (!measured) {
212
+ return {
213
+ ...base,
214
+ classification: "unknown",
215
+ reason: "one or more epoch paths are unreadable",
216
+ };
217
+ }
218
+ return {
219
+ ...base,
220
+ bytes: measured.bytes,
221
+ last_modified_at: new Date(measured.lastModifiedMs).toISOString(),
222
+ };
223
+ } catch (error) {
224
+ return {
225
+ ...base,
226
+ classification: "unknown",
227
+ reason: `cannot inspect archive: ${error instanceof Error ? error.message : String(error)}`,
228
+ };
229
+ }
230
+ }
231
+
232
+ function measureTree(path: string): { bytes: number; lastModifiedMs: number } | null {
233
+ try {
234
+ const stat = lstatSync(path);
235
+ if (stat.isSymbolicLink() || stat.isFile()) {
236
+ return { bytes: stat.size, lastModifiedMs: stat.mtimeMs };
237
+ }
238
+ if (!stat.isDirectory()) return { bytes: 0, lastModifiedMs: stat.mtimeMs };
239
+ let bytes = stat.size;
240
+ let lastModifiedMs = stat.mtimeMs;
241
+ for (const child of readdirSync(path)) {
242
+ const measured = measureTree(join(path, child));
243
+ if (!measured) return null;
244
+ bytes += measured.bytes;
245
+ lastModifiedMs = Math.max(lastModifiedMs, measured.lastModifiedMs);
246
+ }
247
+ return { bytes, lastModifiedMs };
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
@@ -35,6 +35,7 @@ export type BaseAdapterSignalV3 =
35
35
  | "prompt"
36
36
  | "turn_id"
37
37
  | "turn_completion"
38
+ | "assistant_reply_text"
38
39
  | "tool_request"
39
40
  | "tool_result"
40
41
  | "tool_failure"
@@ -66,6 +67,7 @@ const SHARED_SIGNAL_SUPPORT = {
66
67
  session_start: "native",
67
68
  prompt: "native",
68
69
  turn_completion: "native",
70
+ assistant_reply_text: "native",
69
71
  tool_request: "native",
70
72
  tool_result: "native",
71
73
  tool_failure: "native",
@@ -2,6 +2,15 @@ export {
2
2
  type SchemaAdvanceEligibilityV3,
3
3
  validateAdditiveSchemaAdvanceV3,
4
4
  } from "./advance.ts";
5
+ export {
6
+ autoCleanEventV3Archives,
7
+ cleanEventV3Archives,
8
+ type EventV3ArchiveAutoCleanResult,
9
+ type EventV3ArchiveClassification,
10
+ type EventV3ArchiveEntry,
11
+ eventV3ArchivesRoot,
12
+ inventoryEventV3Archives,
13
+ } from "./archive-retention.ts";
5
14
  export {
6
15
  type AuthorityMutationV3,
7
16
  type AuthorityReceiptV3,
@@ -73,6 +73,7 @@ export function hookSignalV3(eventName: string): HookSignalV3 | undefined {
73
73
  case "session-start":
74
74
  case "session-end":
75
75
  case "user-prompt-submit":
76
+ case "after-agent-response":
76
77
  case "stop":
77
78
  case "stop-failure":
78
79
  case "pre-tool-use":
@@ -19,6 +19,7 @@ export type HookSignalV3Base =
19
19
  | "session-start"
20
20
  | "session-end"
21
21
  | "user-prompt-submit"
22
+ | "after-agent-response"
22
23
  | "stop"
23
24
  | "stop-failure"
24
25
  | "pre-tool-use"
@@ -249,6 +250,11 @@ export function normalizeHookEventV3Base(
249
250
  },
250
251
  }) as EventV3Base;
251
252
  }
253
+ case "after-agent-response":
254
+ // Cursor's completed-response hook contributes a privacy-safe ritual
255
+ // observation to producer state. The authoritative turn.completed event
256
+ // remains the later Stop signal, which consumes that observation.
257
+ return null;
252
258
  case "stop":
253
259
  case "stop-failure":
254
260
  return buildEventV3Base("turn.completed", {
@@ -446,9 +452,15 @@ function turnRitualObservation(
446
452
  ): TurnRitualObservationV3 {
447
453
  if (adapter === "cursor") {
448
454
  const unsupported = { state: "unsupported" as const, capability: "assistant_reply_text" };
455
+ const observed = <T>(value: T) => ({
456
+ state: "observed" as const,
457
+ value,
458
+ attestation: "derived" as const,
459
+ confidence: "exact" as const,
460
+ });
449
461
  return {
450
- status_box_present: unsupported,
451
- status_box_present_strict: unsupported,
462
+ status_box_present: observed(evidence.status_box_present),
463
+ status_box_present_strict: observed(evidence.status_box_present_strict),
452
464
  session_name: unsupported,
453
465
  };
454
466
  }
@@ -44,6 +44,7 @@ const HOOK_SIGNALS: ReadonlySet<string> = new Set([
44
44
  "session-start",
45
45
  "session-end",
46
46
  "user-prompt-submit",
47
+ "after-agent-response",
47
48
  "stop",
48
49
  "stop-failure",
49
50
  "pre-tool-use",