blun-king-cli 9.1.566 → 9.1.569

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 (262) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  4. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  5. package/agent-spine-plugin/README.md +30 -4
  6. package/agent-spine-plugin/blun.plugin.json +3 -3
  7. package/agent-spine-plugin/docs/acceptance.md +61 -0
  8. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  9. package/agent-spine-plugin/docs/host-integration.md +178 -0
  10. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  11. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  12. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  13. package/agent-spine-plugin/docs/releasing.md +85 -0
  14. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  15. package/agent-spine-plugin/docs/source-roots.md +113 -0
  16. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  17. package/agent-spine-plugin/docs/world-model.md +94 -0
  18. package/agent-spine-plugin/hooks/codex.json +2 -2
  19. package/agent-spine-plugin/hooks/hooks.json +1 -1
  20. package/agent-spine-plugin/hooks/version.json +1 -1
  21. package/agent-spine-plugin/package.json +13 -3
  22. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  23. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  24. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  25. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  26. package/agent-spine-plugin/scripts/check-install.js +478 -0
  27. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  28. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  29. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  30. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  31. package/agent-spine-plugin/scripts/release-check.js +145 -0
  32. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  33. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  34. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  35. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  36. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  37. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  38. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  39. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  40. package/agent-spine-plugin/src/cli-agent.js +296 -0
  41. package/agent-spine-plugin/src/cli-attention.js +95 -0
  42. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  43. package/agent-spine-plugin/src/cli-common.js +71 -0
  44. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  45. package/agent-spine-plugin/src/cli-core.js +128 -0
  46. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  47. package/agent-spine-plugin/src/cli-host.js +21 -0
  48. package/agent-spine-plugin/src/cli-learning.js +305 -0
  49. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  50. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  51. package/agent-spine-plugin/src/cli.js +40 -1350
  52. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  53. package/agent-spine-plugin/src/hook.js +185 -567
  54. package/agent-spine-plugin/src/index.js +16 -2
  55. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  56. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  57. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  58. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  59. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  60. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  61. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  62. package/agent-spine-plugin/src/lib/attention.js +9 -600
  63. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  64. package/agent-spine-plugin/src/lib/audit.js +56 -6
  65. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  66. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  67. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  68. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  69. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  70. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  71. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  72. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  73. package/agent-spine-plugin/src/lib/context.js +3 -1
  74. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  75. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  76. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  77. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  78. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  91. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  94. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  95. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  96. package/agent-spine-plugin/src/lib/documents.js +27 -5
  97. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  98. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  99. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  100. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  101. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  102. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  103. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  104. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  105. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  106. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  108. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  109. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  112. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  113. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  114. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  115. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  116. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  117. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  118. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  119. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  120. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  121. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  122. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  123. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  124. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  125. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  126. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  127. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  128. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  129. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  130. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  131. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  132. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  133. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  136. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  137. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  138. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  139. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  140. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  141. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  142. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  143. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  144. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  145. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  146. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  147. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  148. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  149. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  150. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  153. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  154. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  155. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  156. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  158. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  159. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  160. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  161. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  162. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  163. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  164. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  165. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  170. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  171. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  172. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  194. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  195. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  196. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  197. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  198. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  199. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  200. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  201. package/agent-spine-plugin/src/mcp.js +20 -161
  202. package/agent-spine-plugin/src/version.js +1 -1
  203. package/agent-spine-plugin/src/worker.js +22 -5
  204. package/bin/agent-resume-snapshot.cjs +2 -2
  205. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  206. package/bin/agentspine-king-goal-intake.mjs +106 -0
  207. package/bin/baseline-skill-performance-policy.cjs +1 -16
  208. package/bin/core-bootstrap.js +2 -0
  209. package/bin/curiosity-scout-policy.cjs +5 -1
  210. package/bin/input-draft-persistence.cjs +2 -2
  211. package/bin/king-tui-function-contract.json +33 -0
  212. package/bin/launcher-restart-policy.cjs +150 -0
  213. package/bin/launcher-runtime.js +56 -44
  214. package/bin/managed-context-startup-policy.cjs +27 -0
  215. package/bin/managed-plugin-selection.cjs +116 -0
  216. package/bin/mistake-relevance-policy.cjs +1 -1
  217. package/bin/observer-hooks.cjs +14 -0
  218. package/bin/oversized-context-offload-policy.cjs +86 -0
  219. package/bin/plugin-bootstrap.js +7 -40
  220. package/bin/proactive-compaction-policy.cjs +1 -1
  221. package/bin/provider-model-refresh-deadline.cjs +53 -0
  222. package/bin/provider-model-refresh-policy.cjs +107 -0
  223. package/bin/release-artifact-freeze-policy.cjs +30 -0
  224. package/bin/repeated-user-message-projection.cjs +3 -126
  225. package/bin/research-page-result.cjs +74 -0
  226. package/bin/runtime-exit-ledger.cjs +1 -0
  227. package/bin/session-compaction-policy.cjs +84 -0
  228. package/bin/skill-listing-performance-policy.cjs +2 -2
  229. package/bin/standard-tools-bootstrap.js +0 -37
  230. package/bin/subagent-skill-policy.cjs +3 -1
  231. package/bin/telegram-approval-relay.cjs +12 -7
  232. package/bin/telegram-private-conversation-policy.cjs +3 -2
  233. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  234. package/bin/thinking-activity-status-policy.cjs +1 -1
  235. package/bin/thinking-only-guard.cjs +16 -12
  236. package/bin/tool-call-loop-policy.cjs +0 -2
  237. package/bin/tool-result-offload-policy.cjs +11 -2
  238. package/bin/tui-functional-contract.cjs +55 -0
  239. package/bin/update-notice.js +18 -14
  240. package/bin/user-message-offload-policy.cjs +1 -1
  241. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  242. package/bin/windows-node-crash-dump.cjs +110 -0
  243. package/blun.mjs +2517 -1048
  244. package/codebase-index/codebase_index.py +4 -3
  245. package/package.json +3 -17
  246. package/standard-skills/research-evidence/SKILL.md +39 -0
  247. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  248. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  249. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  250. package/standard-skills/web-lesen/SKILL.md +37 -22
  251. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  252. package/telegram-plugin/DELIVERY.md +36 -0
  253. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  254. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  255. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  256. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  257. package/telegram-plugin/dist/bridge.mjs +11 -57
  258. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  259. package/agent-spine-plugin/skill/SKILL.md +0 -76
  260. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  261. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  262. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,500 @@
1
+ import { validateCodexTimelineHeader } from "./session-timeline-codex.js";
2
+ import { validateKingTimelineHeader } from "./session-timeline-king.js";
3
+ import { timelineProtocolFromRuntime, validTimelineHost } from "./session-timeline-provider.js";
4
+ import { createHash } from "node:crypto";
5
+ import { isAbsolute } from "node:path";
6
+ import { canonicalPath } from "./paths.js";
7
+ import { withOwnedFileLock } from "./owned-file-lock.js";
8
+ import {
9
+ privateEnrollmentPaths, readPrivateEnrollmentState, savePrivateEnrollmentState
10
+ } from "./session-timeline-enrollment-storage.js";
11
+ import {
12
+ normalizePrivateTimelineSource, PRIVATE_TIMELINE_PREFIX_BYTES, privateTimelinePrefixDigest
13
+ } from "./session-timeline-enrollment-source.js";
14
+ import {
15
+ hostReceiptId, hostReceiptResult, hostReceiptUnavailable, makeHostTranscriptReceipt, MAX_PENDING_RECEIPTS,
16
+ PRIVATE_TIMELINE_HOST_RECEIPT_ORIGIN, PRIVATE_TIMELINE_HOST_RECEIPT_SCHEMA, purgeExpiredHostReceipts,
17
+ sameHostReceiptSource, sameIssuedHostTranscriptEvent, validHostTranscriptReceipt
18
+ } from "./session-timeline-host-receipt.js";
19
+ import {
20
+ completeTimelineBinding, hasTimelineGroupScope, sameTimelineBinding, sessionTimelineBinding,
21
+ safeTimelineId, TIMELINE_ID_RE, validTimelineBinding
22
+ } from "./session-timeline-contract.js";
23
+ import { sameTimelineTransportDigest, timelineTransportDigest, validTimelineTransportDigest } from "./session-timeline-transport.js";
24
+ import {
25
+ sameSessionTimelineSourceLocation, sourcePath, validSessionTimelineSourceMetadata
26
+ } from "./session-timeline-source.js";
27
+ import { validVerifiedTimelineHostOrigin } from "./session-timeline-host-origin.js";
28
+ import { sessionTimelineRootDigest } from "./session-timeline-root.js";
29
+
30
+ export const PRIVATE_TIMELINE_ENROLLMENT_SCHEMA = "agentspine.session-timeline-private-enrollment/v1";
31
+ export { PRIVATE_TIMELINE_HOST_RECEIPT_ORIGIN, PRIVATE_TIMELINE_HOST_RECEIPT_SCHEMA } from "./session-timeline-host-receipt.js";
32
+ export const LOCAL_TIMELINE_ENROLLMENT_CONFIRMATION = "local-owner-confirmed";
33
+ export const LOCAL_TIMELINE_ENROLLMENT_RECOVERY_CONFIRMATION = "local-owner-confirmed-timeline-enrollment-recovery";
34
+ export const DEFAULT_TIMELINE_ENROLLMENT_TTL_MS = 8 * 60 * 60 * 1000;
35
+ export const MAX_TIMELINE_ENROLLMENT_TTL_MS = 12 * 60 * 60 * 1000;
36
+
37
+ const AUTHORITY = "context-only";
38
+ const TIMELINE_VISIBILITY = "private-verified";
39
+ const HEAD_SCHEMA = "agentspine.session-timeline-private-enrollment-head/v1";
40
+ const HEAD_AUTHORITY = "state-integrity-only";
41
+ const MAX_STATE_BYTES = 256 * 1024;
42
+ const MAX_ENROLLMENTS = 16;
43
+ function canonical(value) {
44
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
45
+ if (value && typeof value === "object") {
46
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
47
+ }
48
+ return JSON.stringify(value);
49
+ }
50
+
51
+ function digest(value) { return createHash("sha256").update(value).digest("hex"); }
52
+ function validSignature(value) { return /^[a-f0-9]{64}$/.test(value || ""); }
53
+ function validGeneration(value) { return Number.isSafeInteger(value) && value > 0; }
54
+ function validDate(value) { return Number.isFinite(new Date(value).getTime()); }
55
+ function date(value) {
56
+ const parsed = value instanceof Date ? value : new Date(value);
57
+ return Number.isFinite(parsed.getTime()) ? parsed : null;
58
+ }
59
+
60
+ function runtimeDate(clock) {
61
+ try { return date(typeof clock === "function" ? clock() : new Date()); }
62
+ catch { return null; }
63
+ }
64
+
65
+ function enrollmentTtl(value) {
66
+ return Number.isInteger(value) && value >= 60 * 1000 && value <= MAX_TIMELINE_ENROLLMENT_TTL_MS ? value : null;
67
+ }
68
+
69
+ function unavailable(reason) {
70
+ return {
71
+ schema: PRIVATE_TIMELINE_ENROLLMENT_SCHEMA, status: "unavailable", reason,
72
+ binding: null, source: null, sourceDigest: null, expiresAt: null,
73
+ enrollmentDigest: null, timelineVisibility: null, authority: AUTHORITY
74
+ };
75
+ }
76
+
77
+ function empty(root) {
78
+ return {
79
+ schema: PRIVATE_TIMELINE_ENROLLMENT_SCHEMA, rootDigest: sessionTimelineRootDigest(root),
80
+ enrollments: [], pendingReceipts: [], generation: 0, previousSignature: null, authority: AUTHORITY
81
+ };
82
+ }
83
+
84
+ function sourceMetadata(source) {
85
+ return {
86
+ path: source?.path, profileRoot: source?.profileRoot, projectsRoot: source?.projectsRoot,
87
+ pathDigest: source?.pathDigest, identity: source?.identity,
88
+ size: source?.size, mtimeNs: source?.mtimeNs, ctimeNs: source?.ctimeNs
89
+ };
90
+ }
91
+
92
+ function validSource(source) {
93
+ return validSessionTimelineSourceMetadata(sourceMetadata(source))
94
+ && Number.isSafeInteger(source.prefixBytes) && source.prefixBytes >= 0 && source.prefixBytes <= PRIVATE_TIMELINE_PREFIX_BYTES
95
+ && /^[a-f0-9]{64}$/.test(source.prefixDigest || "")
96
+ && source.commitment === null;
97
+ }
98
+
99
+ function sourceDigest(source) {
100
+ return digest(`${source.pathDigest}\0${source.identity}\0${source.profileRoot}\0${source.projectsRoot}\0${source.size}\0${source.mtimeNs}\0${source.ctimeNs}\0${source.prefixDigest}\0lazy-snapshot/v1`);
101
+ }
102
+
103
+ function sameSource(left, right) { return sameSessionTimelineSourceLocation(sourceMetadata(left), sourceMetadata(right)); }
104
+
105
+ function sameSourceSnapshot(left, right) {
106
+ const first = sourceMetadata(left);
107
+ const second = sourceMetadata(right);
108
+ return validSessionTimelineSourceMetadata(first) && validSessionTimelineSourceMetadata(second)
109
+ && ["path", "profileRoot", "projectsRoot", "pathDigest", "identity", "size", "mtimeNs", "ctimeNs"]
110
+ .every((key) => first[key] === second[key]);
111
+ }
112
+
113
+ function enrollmentId(root, binding, source) {
114
+ return `timeline-enrollment:${digest(canonical({ rootDigest: sessionTimelineRootDigest(root), binding, sourceDigest: sourceDigest(source) })).slice(0, 32)}`;
115
+ }
116
+
117
+ function enrollmentDigest(root, binding, source, expiresAt, transportDigest) {
118
+ return digest(canonical({ rootDigest: sessionTimelineRootDigest(root), binding, sourceDigest: sourceDigest(source), expiresAt,
119
+ transportDigest, confirmation: LOCAL_TIMELINE_ENROLLMENT_CONFIRMATION, timelineVisibility: TIMELINE_VISIBILITY }));
120
+ }
121
+
122
+ function validEnrollment(value, root) {
123
+ return value && TIMELINE_ID_RE.test(value.id || "") && validTimelineBinding(value.binding)
124
+ && completeTimelineBinding(value.binding) && value.binding.groupId === null && validSource(value.source)
125
+ && validDate(value.confirmedAt) && validDate(value.expiresAt) && value.timelineVisibility === TIMELINE_VISIBILITY
126
+ && value.confirmation === LOCAL_TIMELINE_ENROLLMENT_CONFIRMATION && value.authority === AUTHORITY
127
+ && validTimelineTransportDigest(value.transportDigest)
128
+ && value.id === enrollmentId(root, value.binding, value.source)
129
+ && value.enrollmentDigest === enrollmentDigest(root, value.binding, value.source, value.expiresAt, value.transportDigest)
130
+ && value.append === undefined;
131
+ }
132
+
133
+ function unambiguousEnrollments(enrollments) {
134
+ const keys = new Set();
135
+ for (const record of enrollments) {
136
+ const key = [record.binding.host, record.binding.sessionId, sourceDigest(record.source)].join("\0");
137
+ if (keys.has(key)) return false;
138
+ keys.add(key);
139
+ }
140
+ return true;
141
+ }
142
+
143
+ function validate(state, root) {
144
+ const receipts = state?.pendingReceipts === undefined ? [] : state.pendingReceipts;
145
+ if (!state || state.schema !== PRIVATE_TIMELINE_ENROLLMENT_SCHEMA || state.rootDigest !== sessionTimelineRootDigest(root)
146
+ || state.authority !== AUTHORITY || !Array.isArray(state.enrollments)
147
+ || !validGeneration(state.generation) || !validSignature(state.signature)
148
+ || (state.generation === 1 ? state.previousSignature !== null : !validSignature(state.previousSignature))
149
+ || state.enrollments.length > MAX_ENROLLMENTS || !state.enrollments.every((item) => validEnrollment(item, root))
150
+ || !unambiguousEnrollments(state.enrollments) || !Array.isArray(receipts) || receipts.length > MAX_PENDING_RECEIPTS
151
+ || !receipts.every((item) => validHostTranscriptReceipt(item, root))
152
+ || new Set(receipts.map((item) => item.id)).size !== receipts.length) {
153
+ throw new Error("private timeline enrollment state is invalid");
154
+ }
155
+ return state;
156
+ }
157
+
158
+ function stateRoot(root) { return { value: root, digest: sessionTimelineRootDigest(root) }; }
159
+
160
+ async function enrollmentPaths(root, options = {}) {
161
+ return privateEnrollmentPaths(root, options);
162
+ }
163
+
164
+ async function readState(path, headPath, root, assertStable, assertOwned = null) {
165
+ const state = await readPrivateEnrollmentState({
166
+ path, headPath, root: stateRoot(root), assertStable, assertOwned, empty, validate,
167
+ headSchema: HEAD_SCHEMA, headAuthority: HEAD_AUTHORITY, maximumBytes: MAX_STATE_BYTES
168
+ });
169
+ if (!Array.isArray(state.pendingReceipts)) state.pendingReceipts = [];
170
+ return state;
171
+ }
172
+
173
+ async function saveState(state, path, headPath, root, assertOwned, assertStable) {
174
+ await savePrivateEnrollmentState({
175
+ state, path, headPath, root: stateRoot(root), assertOwned, assertStable,
176
+ headSchema: HEAD_SCHEMA, headAuthority: HEAD_AUTHORITY, maximumBytes: MAX_STATE_BYTES
177
+ });
178
+ }
179
+
180
+ // The normal path remains read-only. If a crash left one verified state
181
+ // generation ahead of its signed head, retry under the existing owned lock so
182
+ // storage can perform its exact one-step repair; every other failure remains
183
+ // unavailable and yields no enrollment or source data.
184
+ async function loadStateWithRecovery(rootPath, { create = false } = {}) {
185
+ const names = await enrollmentPaths(rootPath, { create });
186
+ try {
187
+ return { names, state: await readState(names.path, names.headPath, rootPath, names.assertStable) };
188
+ } catch {
189
+ const state = await withOwnedFileLock(names.lock, async ({ assertOwned }) => readState(
190
+ names.path, names.headPath, rootPath, names.assertStable, assertOwned
191
+ ), { assertPath: names.assertStable });
192
+ return { names, state };
193
+ }
194
+ }
195
+
196
+ function validHostReceiptInput({ host, sessionId, scope }) {
197
+ const binding = sessionTimelineBinding({ host, sessionId, scope });
198
+ if (!validTimelineHost(host)) return { reason: "unknown-timeline-host" };
199
+ if (scope?.groupId !== null || hasTimelineGroupScope(scope)) return { reason: "group-suppressed" };
200
+ if (!completeTimelineBinding(binding) || binding.groupId !== null) return { reason: "missing-or-unknown-session-scope" };
201
+ return { binding };
202
+ }
203
+
204
+ function validResolverInput({ host, sessionId }) {
205
+ if (!validTimelineHost(host)) return null;
206
+ return safeTimelineId(sessionId);
207
+ }
208
+
209
+ async function canonicalRoot(root) {
210
+ if (typeof root !== "string" || !root) return null;
211
+ try { return await canonicalPath(root); }
212
+ catch { return null; }
213
+ }
214
+
215
+ // This is called only by a verified provider UserPromptSubmit lifecycle adapter. It
216
+ // records a signed, short-lived observation; the opaque token itself is never
217
+ // returned through hook context or a briefing.
218
+ export async function issueHostTranscriptReceipt({
219
+ root, host, sessionId, scope, transcriptPath, hostHome, event, eventId = null,
220
+ hostOrigin = null, clock = null, environment = process.env
221
+ }) {
222
+ const current = runtimeDate(clock);
223
+ if (!current) return hostReceiptUnavailable("invalid-host-receipt-time");
224
+ if (event !== "UserPromptSubmit") return hostReceiptUnavailable("host-receipt-event-not-supported");
225
+ const rootPath = await canonicalRoot(root);
226
+ if (!rootPath) return hostReceiptUnavailable("unknown-project-root");
227
+ const input = validHostReceiptInput({ host, sessionId, scope });
228
+ if (!input.binding) return hostReceiptUnavailable(input.reason);
229
+ const hookInput = { transcript_path: transcriptPath, event_id: eventId,
230
+ protocol_version: timelineProtocolFromRuntime(host, environment) };
231
+ if (!validVerifiedTimelineHostOrigin({ origin: hostOrigin, root: rootPath, input: hookInput,
232
+ binding: input.binding, hostHome, event })) return hostReceiptUnavailable("host-lifecycle-receipt-required");
233
+ const transportDigest = timelineTransportDigest({ root: rootPath, binding: input.binding, environment });
234
+ if (!transportDigest) return hostReceiptUnavailable("local-transport-capability-required");
235
+ const source = await sourcePath(transcriptPath, hostHome, host);
236
+ if (source.status !== "registered") return hostReceiptUnavailable(source.reason || "transcript-unavailable");
237
+ if (host === "codex" && !await validateCodexTimelineHeader({ source, root: rootPath, sessionId })) {
238
+ return hostReceiptUnavailable("codex-history-format-or-binding-mismatch");
239
+ }
240
+ if (host === "king" && !await validateKingTimelineHeader({ source, sessionId,
241
+ protocolVersion: timelineProtocolFromRuntime(host, environment) })) {
242
+ return hostReceiptUnavailable("king-history-format-version-or-binding-mismatch");
243
+ }
244
+ // This bounded 4 KiB digest is an immutable source binding, not transcript
245
+ // capture: receipt issuance never retains or injects the source text.
246
+ const snapshot = await normalizePrivateTimelineSource(source);
247
+ if (!snapshot) return hostReceiptUnavailable("transcript-snapshot-unavailable");
248
+ try {
249
+ const names = await enrollmentPaths(rootPath);
250
+ return await withOwnedFileLock(names.lock, async ({ assertOwned }) => {
251
+ const state = await readState(names.path, names.headPath, rootPath, names.assertStable, assertOwned);
252
+ const changed = purgeExpiredHostReceipts(state, current);
253
+ const receipt = makeHostTranscriptReceipt({ root: rootPath, binding: input.binding, source: snapshot,
254
+ transportDigest, current, eventId });
255
+ const existing = state.pendingReceipts.find((item) => sameIssuedHostTranscriptEvent(item, receipt));
256
+ if (existing) {
257
+ if (changed) await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
258
+ return hostReceiptResult(existing);
259
+ }
260
+ if (state.pendingReceipts.length >= MAX_PENDING_RECEIPTS) {
261
+ if (changed) await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
262
+ return hostReceiptUnavailable("host-receipt-capacity-exhausted");
263
+ }
264
+ state.pendingReceipts.unshift(receipt);
265
+ await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
266
+ return hostReceiptResult(receipt);
267
+ }, { assertPath: names.assertStable });
268
+ } catch {
269
+ return hostReceiptUnavailable("host-receipt-state-unavailable");
270
+ }
271
+ }
272
+
273
+ // The local owner can retrieve only the opaque current-session token. The
274
+ // protected transport capability proves the caller's host session; no source
275
+ // path, scope, or transcript content is returned to a generic CLI process.
276
+ export async function currentHostTranscriptReceipt({ root, clock = null, environment = process.env }) {
277
+ const current = runtimeDate(clock);
278
+ if (!current) return hostReceiptUnavailable("invalid-host-receipt-time");
279
+ const rootPath = await canonicalRoot(root);
280
+ if (!rootPath) return hostReceiptUnavailable("unknown-project-root");
281
+ try {
282
+ const { state } = await loadStateWithRecovery(rootPath);
283
+ const matches = state.pendingReceipts.filter((item) => {
284
+ if (new Date(item.expiresAt).getTime() <= current.getTime()) return false;
285
+ const transportDigest = timelineTransportDigest({ root: rootPath, binding: item.binding, environment });
286
+ return transportDigest && sameTimelineTransportDigest(item.transportDigest, transportDigest);
287
+ });
288
+ return matches.length === 1 ? hostReceiptResult(matches[0])
289
+ : hostReceiptUnavailable(matches.length ? "host-receipt-ambiguous" : "host-receipt-unavailable");
290
+ } catch {
291
+ return hostReceiptUnavailable("host-receipt-state-unavailable");
292
+ }
293
+ }
294
+
295
+ // Internal state-only lookup for bootstrap and lifecycle hints. It reads the
296
+ // signed enrollment record but deliberately never opens, stats, or hashes the
297
+ // transcript; callers still need the normal resolver before any evidence read.
298
+ export async function loadPrivateSessionTimelineEnrollment({
299
+ root, host = null, sessionId = null, scope = null, enrollmentDigest: wantedDigest = null,
300
+ expectedTransportDigest = null, clock = null
301
+ }) {
302
+ const current = runtimeDate(clock);
303
+ if (!current) return inspectUnavailable("invalid-enrollment-time");
304
+ const rootPath = await canonicalRoot(root);
305
+ if (!rootPath || (wantedDigest !== null && !/^[a-f0-9]{64}$/.test(wantedDigest))) {
306
+ return inspectUnavailable("private-enrollment-unavailable");
307
+ }
308
+ const requested = scope === null ? null : validHostReceiptInput({ host, sessionId, scope });
309
+ if ((scope !== null || host !== null || sessionId !== null) && !requested?.binding) {
310
+ return inspectUnavailable("private-enrollment-unavailable");
311
+ }
312
+ try {
313
+ const { state } = await loadStateWithRecovery(rootPath);
314
+ const matches = state.enrollments.filter((record) => (!requested || sameTimelineBinding(record.binding, requested.binding))
315
+ && (wantedDigest === null || record.enrollmentDigest === wantedDigest));
316
+ if (matches.length !== 1 || new Date(matches[0].expiresAt).getTime() <= current.getTime()) {
317
+ return inspectUnavailable("private-enrollment-unavailable");
318
+ }
319
+ const [record] = matches;
320
+ if (expectedTransportDigest !== null && (!validTimelineTransportDigest(expectedTransportDigest)
321
+ || !sameTimelineTransportDigest(record.transportDigest, expectedTransportDigest))) {
322
+ return inspectUnavailable("private-enrollment-transport-mismatch");
323
+ }
324
+ return { status: "loaded", rootPath, record, now: current };
325
+ } catch {
326
+ return inspectUnavailable("private-enrollment-state-unavailable");
327
+ }
328
+ }
329
+
330
+ function activeResult(record, activeSource) {
331
+ return {
332
+ schema: PRIVATE_TIMELINE_ENROLLMENT_SCHEMA, status: "enrolled", id: record.id,
333
+ binding: { ...record.binding }, source: {
334
+ path: activeSource.path, profileRoot: activeSource.profileRoot, projectsRoot: activeSource.projectsRoot,
335
+ pathDigest: activeSource.pathDigest, identity: activeSource.identity, size: activeSource.size,
336
+ mtimeNs: activeSource.mtimeNs, ctimeNs: activeSource.ctimeNs,
337
+ commitmentDigest: null, committedBytes: 0
338
+ }, sourceDigest: sourceDigest(record.source), enrollmentDigest: record.enrollmentDigest,
339
+ expiresAt: record.expiresAt, timelineVisibility: record.timelineVisibility, authority: AUTHORITY
340
+ };
341
+ }
342
+
343
+ function inspectUnavailable(reason) { return { status: "unavailable", reason }; }
344
+
345
+ export async function inspectPrivateSessionTimelineEnrollment({
346
+ root, host, sessionId, transcriptPath, hostHome, clock = null, expectedTransportDigest = null
347
+ }) {
348
+ const current = runtimeDate(clock);
349
+ if (!current) return inspectUnavailable("invalid-enrollment-time");
350
+ const rootPath = await canonicalRoot(root);
351
+ if (!rootPath) return inspectUnavailable("unknown-project-root");
352
+ const activeSessionId = validResolverInput({ host, sessionId });
353
+ if (!activeSessionId) return inspectUnavailable("unknown-timeline-host-or-session");
354
+ try {
355
+ const { state } = await loadStateWithRecovery(rootPath);
356
+ const candidates = state.enrollments.filter((item) => item.binding.host === host && item.binding.sessionId === activeSessionId);
357
+ const requestedPath = typeof transcriptPath === "string" && transcriptPath
358
+ ? transcriptPath : candidates.length === 1 ? candidates[0].source.path : null;
359
+ // Post-compaction input may omit a host profile root. In that case, the
360
+ // sealed root from the one matching enrollment is the only allowed
361
+ // fallback; an environment or arbitrary caller path is never consulted.
362
+ const trustedHome = hostHome === null || hostHome === undefined
363
+ ? candidates.length === 1 ? candidates[0].source.profileRoot : null
364
+ : isAbsolute(hostHome) ? hostHome : null;
365
+ if (!requestedPath || !trustedHome) return inspectUnavailable("private-enrollment-unavailable");
366
+ const active = await sourcePath(requestedPath, trustedHome, host);
367
+ if (active.status !== "registered") return inspectUnavailable(active.reason || "transcript-unavailable");
368
+ const matches = candidates.filter((item) => sameSource(item.source, active));
369
+ if (matches.length !== 1) return inspectUnavailable("private-enrollment-unavailable");
370
+ const [record] = matches;
371
+ if (new Date(record.expiresAt).getTime() <= current.getTime()) return inspectUnavailable("private-enrollment-expired");
372
+ if (expectedTransportDigest !== null && (!validTimelineTransportDigest(expectedTransportDigest)
373
+ || !sameTimelineTransportDigest(record.transportDigest, expectedTransportDigest))) {
374
+ return inspectUnavailable("private-enrollment-transport-mismatch");
375
+ }
376
+ return { status: "loaded", rootPath, record, active, now: current };
377
+ } catch {
378
+ return inspectUnavailable("private-enrollment-state-unavailable");
379
+ }
380
+ }
381
+
382
+ export async function mutatePrivateSessionTimelineEnrollment({ root, task }) {
383
+ const rootPath = await canonicalRoot(root);
384
+ if (!rootPath || typeof task !== "function") return null;
385
+ try {
386
+ const names = await enrollmentPaths(rootPath);
387
+ return await withOwnedFileLock(names.lock, async ({ assertOwned }) => {
388
+ const state = await readState(names.path, names.headPath, rootPath, names.assertStable, assertOwned);
389
+ let committed = false;
390
+ const commit = async () => {
391
+ if (committed) return;
392
+ await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
393
+ committed = true;
394
+ };
395
+ return task({ rootPath, state, commit });
396
+ }, { assertPath: names.assertStable });
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+
402
+ // A first state write interrupted before any signed head exists is not
403
+ // distinguishable from a deleted head. It is never auto-accepted: a local
404
+ // owner may explicitly discard all enrollment state, then obtain a fresh host
405
+ // receipt. This function exposes no historic binding, path, or transcript.
406
+ export async function resetPrivateSessionTimelineEnrollment({ root, confirmation }) {
407
+ if (confirmation !== LOCAL_TIMELINE_ENROLLMENT_RECOVERY_CONFIRMATION) return false;
408
+ const rootPath = await canonicalRoot(root);
409
+ if (!rootPath) return false;
410
+ try {
411
+ const names = await enrollmentPaths(rootPath);
412
+ return await withOwnedFileLock(names.lock, async ({ assertOwned }) => {
413
+ try {
414
+ await readState(names.path, names.headPath, rootPath, names.assertStable, assertOwned);
415
+ return false;
416
+ } catch {
417
+ const state = empty(rootPath);
418
+ await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
419
+ return true;
420
+ }
421
+ }, { assertPath: names.assertStable });
422
+ } catch {
423
+ return false;
424
+ }
425
+ }
426
+
427
+ export async function enrollPrivateSessionTimeline(options = {}) {
428
+ const { root, hostReceipt, confirmation, ttlMs = DEFAULT_TIMELINE_ENROLLMENT_TTL_MS,
429
+ clock = null, environment = process.env } = options || {};
430
+ if (!options || typeof options !== "object" || Array.isArray(options)
431
+ || ["host", "sessionId", "scope", "transcriptPath", "hostHome"].some((key) => options[key] !== undefined)) {
432
+ return unavailable("host-transcript-receipt-required");
433
+ }
434
+ if (confirmation !== LOCAL_TIMELINE_ENROLLMENT_CONFIRMATION) return unavailable("local-owner-confirmation-required");
435
+ if (!hostReceiptId(hostReceipt)) return unavailable("host-transcript-receipt-required");
436
+ const current = runtimeDate(clock);
437
+ if (!current) return unavailable("invalid-enrollment-time");
438
+ const ttl = enrollmentTtl(ttlMs);
439
+ if (!ttl) return unavailable("invalid-enrollment-expiry");
440
+ const rootPath = await canonicalRoot(root);
441
+ if (!rootPath) return unavailable("unknown-project-root");
442
+ try {
443
+ const names = await enrollmentPaths(rootPath);
444
+ let result = unavailable("host-transcript-receipt-unavailable");
445
+ await withOwnedFileLock(names.lock, async ({ assertOwned }) => {
446
+ const state = await readState(names.path, names.headPath, rootPath, names.assertStable, assertOwned);
447
+ const pruned = purgeExpiredHostReceipts(state, current);
448
+ const receiptIndex = state.pendingReceipts.findIndex((item) => item.id === hostReceipt);
449
+ const receipt = receiptIndex < 0 ? null : state.pendingReceipts[receiptIndex];
450
+ if (!receipt) {
451
+ if (pruned) await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
452
+ return;
453
+ }
454
+ const transportDigest = timelineTransportDigest({ root: rootPath, binding: receipt.binding, environment });
455
+ if (!transportDigest || !sameTimelineTransportDigest(receipt.transportDigest, transportDigest)) {
456
+ result = unavailable("local-transport-capability-required"); return;
457
+ }
458
+ const source = await sourcePath(receipt.source.path, receipt.source.profileRoot, receipt.binding.host);
459
+ const normalized = source.status === "registered" ? await normalizePrivateTimelineSource(source) : null;
460
+ state.pendingReceipts.splice(receiptIndex, 1);
461
+ if (!normalized || !sameHostReceiptSource(receipt, normalized)) {
462
+ await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
463
+ result = unavailable("transcript-snapshot-changed"); return;
464
+ }
465
+ const record = { binding: receipt.binding, source: normalized, confirmedAt: current.toISOString(),
466
+ expiresAt: new Date(current.getTime() + ttl).toISOString(), timelineVisibility: TIMELINE_VISIBILITY,
467
+ confirmation: LOCAL_TIMELINE_ENROLLMENT_CONFIRMATION, transportDigest: receipt.transportDigest, authority: AUTHORITY };
468
+ record.id = enrollmentId(rootPath, record.binding, record.source);
469
+ record.enrollmentDigest = enrollmentDigest(rootPath, record.binding, record.source, record.expiresAt, record.transportDigest);
470
+ state.enrollments = state.enrollments.filter((item) => new Date(item.expiresAt).getTime() > current.getTime()
471
+ && !(sameTimelineBinding(item.binding, record.binding)
472
+ || (item.binding.host === record.binding.host && item.binding.sessionId === record.binding.sessionId
473
+ && sameSource(item.source, record.source))));
474
+ state.enrollments.unshift(record);
475
+ state.enrollments = state.enrollments.slice(0, MAX_ENROLLMENTS);
476
+ await saveState(state, names.path, names.headPath, rootPath, assertOwned, names.assertStable);
477
+ result = activeResult(record, source);
478
+ }, { assertPath: names.assertStable });
479
+ return result;
480
+ } catch {
481
+ return unavailable("private-enrollment-state-unavailable");
482
+ }
483
+ }
484
+
485
+ export async function resolvePrivateSessionTimelineEnrollment({
486
+ root, host, sessionId, transcriptPath, hostHome, clock = null, expectedTransportDigest = null
487
+ }) {
488
+ const inspected = await inspectPrivateSessionTimelineEnrollment({
489
+ root, host, sessionId, transcriptPath, hostHome, clock, expectedTransportDigest
490
+ });
491
+ if (inspected.status !== "loaded") return unavailable(inspected.reason);
492
+ const { record, active } = inspected;
493
+ if (!sameSourceSnapshot(record.source, active)) {
494
+ return unavailable("transcript-snapshot-renewal-required");
495
+ }
496
+ if (await privateTimelinePrefixDigest(active, record.source.prefixBytes) !== record.source.prefixDigest) {
497
+ return unavailable("transcript-replaced");
498
+ }
499
+ return activeResult(record, active);
500
+ }
@@ -0,0 +1,157 @@
1
+ import { codexTimelineToolResult } from "./session-timeline-codex.js";
2
+ import { kingTimelineToolResult } from "./session-timeline-king.js";
3
+ import { createHash } from "node:crypto";
4
+ import { timelineTerms } from "./session-timeline-query.js";
5
+
6
+ const MAX_LINE_BYTES = 1024 * 1024;
7
+ const TEXT_KEY_RE = /^(?:content|text|result|output|error|message)$/i;
8
+ const SENSITIVE_KEY_RE = /^(?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|token|secret|client[_-]?secret|password|passwort|credential|authorization|proxy[_-]?authorization|cookie|set[_-]?cookie|private[_-]?key|aws[_-]?secret[_-]?access[_-]?key)$/i;
9
+ const SECRET_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)|\b(?:aspt|astp|astc)_[A-Za-z0-9_.-]{40,}\b|\b(?:sk[-_](?:proj[-_])?|gh[opusu]_)[A-Za-z0-9_-]{20,}\b|\b(?:xox[bapcrs]-|github_pat_|glpat-|npm_)[A-Za-z0-9_-]{12,}\b|\bAKIA[0-9A-Z]{16}\b|\b[a-z][a-z0-9+.-]{0,31}:\/\/[^\s\/@:]+:[^\s\/@]+@|https?:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/-]+|\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/i;
10
+ const CREDENTIAL_VALUE_RE = /["'](?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|token|secret|client[_-]?secret|password|passwort|credential|authorization|proxy[_-]?authorization|cookie|set[_-]?cookie|private[_-]?key|aws[_-]?secret[_-]?access[_-]?key)["']\s*:\s*(?:["'][^"'\r\n]*["']|[^,}\]\s]+)|\b(?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|token|secret|client[_-]?secret|password|passwort|credential|aws[_-]?secret[_-]?access[_-]?key)\s*[:=]\s*(?:["'][^"'\r\n]*["']|\S+)/i;
11
+ const AUTHORIZATION_RE = /\b(?:authorization|proxy-authorization)\s*:\s*(?:bearer|basic|digest|token|apikey|api-key|aws4-hmac-sha256)\b|\b(?:bearer|basic|digest|token|apikey|api-key|aws4-hmac-sha256)\s+[A-Za-z0-9._~+\/=:-]+/i;
12
+ const INSTRUCTION_RE = /(?:\b(?:ignore|disregard|override|bypass|forget)\b[\s\S]{0,180}\b(?:previous|prior|system|developer|instruction|instructions|prompt|prompts|rule|rules|safeguard|safeguards)\b|\b(?:ignoriere|missachte|übergehe|umgehe|vergesse)\b[\s\S]{0,180}\b(?:vorherige|frühere|system|entwickler|anweisung|anweisungen|prompt|prompts|regel|regeln|schutzmaßnahme|schutzmaßnahmen)\b|<\s*\/?\s*(?:system|developer|assistant|instructions?|prompt)\b[^>]*>)/i;
13
+ const OBJECTIVE_RE = /\b(?:measur(?:e|ed|ement)|gemessen|benchmark|test(?:ed|ing)?|suite|assert(?:ion)?|ci\b|prüfung|durchlauf|ergebnis)\b/i;
14
+ const OUTCOME_PATTERNS = [
15
+ ["pass", /\b(?:pass(?:ed|ing)?|green|success(?:ful)?|ok|bestanden|erfolgreich)\b/gi],
16
+ ["fail", /\b(?:fail(?:ed|ure)?|red|fehlgeschlagen)\b/gi],
17
+ ["blocked", /\b(?:blocked|blockiert)\b/gi],
18
+ ["timeout", /\b(?:timeout|zeitüberschreitung)\b/gi],
19
+ ["error", /\b(?:error|fehler)\b/gi],
20
+ ["skipped", /\b(?:skipped|übersprungen)\b/gi]
21
+ ];
22
+
23
+ function digest(value) { return createHash("sha256").update(value).digest("hex"); }
24
+
25
+ export function extractTimelineTimestamp(value) {
26
+ for (const key of ["timestamp", "created_at", "createdAt"]) {
27
+ const current = value?.[key];
28
+ if (typeof current !== "string" || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?Z$/.test(current)) continue;
29
+ const parsed = new Date(current);
30
+ if (Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 19) === current.slice(0, 19)) return parsed.toISOString();
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function isToolResult(value) {
36
+ const type = String(value?.type || value?.kind || "").toLowerCase().replaceAll("_", "-");
37
+ return value?.role === "tool" || ["tool-result", "toolresult", "command-result", "runner-result"].includes(type);
38
+ }
39
+
40
+ function unsafeText(value) {
41
+ return SECRET_RE.test(value) || CREDENTIAL_VALUE_RE.test(value) || AUTHORIZATION_RE.test(value) || INSTRUCTION_RE.test(value);
42
+ }
43
+
44
+ function unsafeObject(value) {
45
+ const pending = [{ value, depth: 0 }];
46
+ let visited = 0;
47
+ while (pending.length && visited < 128) {
48
+ const current = pending.pop();
49
+ visited += 1;
50
+ if (typeof current.value === "string") {
51
+ if (unsafeText(current.value)) return true;
52
+ continue;
53
+ }
54
+ if (!current.value || typeof current.value !== "object" || current.depth > 5) continue;
55
+ for (const [key, item] of Object.entries(current.value).slice(0, 64)) {
56
+ if (SENSITIVE_KEY_RE.test(key)) return true;
57
+ pending.push({ value: item, depth: current.depth + 1 });
58
+ }
59
+ }
60
+ return visited >= 128;
61
+ }
62
+
63
+ function objectiveText(value) {
64
+ const values = [];
65
+ const visit = (item, key = "", depth = 0) => {
66
+ if (depth > 5 || values.length >= 12) return;
67
+ if (typeof item === "string") {
68
+ if (TEXT_KEY_RE.test(key)) values.push(item.slice(0, 4096));
69
+ return;
70
+ }
71
+ if (!item || typeof item !== "object") return;
72
+ for (const [name, current] of Object.entries(item).slice(0, 48)) visit(current, name, depth + 1);
73
+ };
74
+ visit(value);
75
+ return values.join("\n");
76
+ }
77
+
78
+ function normalizedOutcome(value) {
79
+ const found = OUTCOME_PATTERNS.filter(([, pattern]) => {
80
+ pattern.lastIndex = 0;
81
+ return pattern.test(value);
82
+ }).map(([outcome]) => outcome);
83
+ return found.length === 1 ? found[0] : null;
84
+ }
85
+
86
+ function normalizedCount(value) {
87
+ const matches = [...value.matchAll(/\b(\d{1,6})\s*\/\s*(\d{1,6})\b/g)].map((match) => ({
88
+ value: Number(match[1]), total: Number(match[2])
89
+ })).filter((item) => item.value <= item.total && item.total > 0);
90
+ if (!matches.length) return null;
91
+ const first = matches[0];
92
+ return matches.every((item) => item.value === first.value && item.total === first.total) ? first : null;
93
+ }
94
+
95
+ function normalizedTestLabel(value) {
96
+ const suite = value.match(/\b(?:suite|test(?:\s+suite)?|testlauf|prüfung)\s*(?:#|nr\.?|nummer)?\s*(\d{1,4})\b/i);
97
+ if (suite) return `suite-${Number(suite[1])}`;
98
+ if (/\bacceptance\b/i.test(value)) return "acceptance";
99
+ if (/\baudit\b/i.test(value)) return "audit";
100
+ if (/\bnpm\s+run\s+check\b/i.test(value)) return "npm-check";
101
+ if (/\bci\b/i.test(value)) return "ci";
102
+ if (/\b(?:test|prüfung|durchlauf)\b/i.test(value)) return "test";
103
+ return null;
104
+ }
105
+
106
+ function structuredTerms({ outcome, count, testLabel }) {
107
+ return timelineTerms(["objective", "measurement", "messung", "result", "ergebnis", outcome, testLabel || "",
108
+ count ? String(count.value) : "", count ? String(count.total) : ""].join(" "));
109
+ }
110
+
111
+ function objectiveExcerpt(body) {
112
+ const segments = body.split(/(?:\r?\n)+|(?<=[.!?])\s+/u).map((item) => item.trim()).filter(Boolean);
113
+ const selected = segments.find((item) => OBJECTIVE_RE.test(item) && normalizedOutcome(item));
114
+ return (selected || "").replace(/\s+/g, " ").slice(0, 320);
115
+ }
116
+
117
+ function candidateFromToolResult(value) {
118
+ if (!isToolResult(value) || unsafeObject(value)) return null;
119
+ const body = objectiveText(value);
120
+ if (!body || !OBJECTIVE_RE.test(body)) return null;
121
+ const outcome = normalizedOutcome(body);
122
+ if (!outcome) return null;
123
+ const count = normalizedCount(body);
124
+ const testLabel = normalizedTestLabel(body);
125
+ return { kind: "objective-result", outcome, count, testLabel, terms: structuredTerms({ outcome, count, testLabel }) };
126
+ }
127
+
128
+ function timelineEventFromLine(line, offset, authority, includeExcerpt, host) {
129
+ const source = Buffer.from(line);
130
+ if (source.byteLength > MAX_LINE_BYTES) return null;
131
+ let parsed;
132
+ try { parsed = JSON.parse(line.trim()); } catch {
133
+ if (["codex", "king"].includes(host)) throw new Error(`${host}-history-format-mismatch`);
134
+ return null;
135
+ }
136
+ const tool = host === "codex" ? codexTimelineToolResult(parsed)
137
+ : host === "king" ? kingTimelineToolResult(parsed) : null;
138
+ const at = tool?.at || extractTimelineTimestamp(parsed);
139
+ if (!at || unsafeObject(parsed)) return null;
140
+ const candidate = ["codex", "king"].includes(host) ? candidateFromToolResult(tool)
141
+ : candidateFromToolResult(parsed) || candidateFromToolResult(parsed.message);
142
+ if (!candidate) return null;
143
+ const stable = JSON.stringify({ at, ...candidate });
144
+ const event = { id: `timeline-event:${digest(`${offset}\0${stable}`).slice(0, 32)}`, at, offset, bytes: source.byteLength,
145
+ sha256: digest(source), ...candidate, authority };
146
+ if (tool) event.nativeMessageId = tool.nativeMessageId;
147
+ if (includeExcerpt) event.excerpt = objectiveExcerpt(objectiveText(tool || parsed));
148
+ return event;
149
+ }
150
+
151
+ export function eventFromTimelineLine(line, offset, authority = "context-only", host = "claude") {
152
+ return timelineEventFromLine(line, offset, authority, false, host);
153
+ }
154
+
155
+ export function verifiedTimelineEventFromLine(line, offset, authority = "context-only", host = "claude") {
156
+ return timelineEventFromLine(line, offset, authority, true, host);
157
+ }