@noopolis/daimon 0.2.0 → 0.2.2

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 (261) hide show
  1. package/README.md +6 -0
  2. package/dist/contracts/attentionContract.d.ts +25 -0
  3. package/dist/contracts/attentionContract.d.ts.map +1 -1
  4. package/dist/contracts/attentionContract.js +8 -1
  5. package/dist/contracts/grokBrokerTaskClock.d.ts +18 -0
  6. package/dist/contracts/grokBrokerTaskClock.d.ts.map +1 -0
  7. package/dist/contracts/grokBrokerTaskClock.js +16 -0
  8. package/dist/contracts/organizationRuntimeContract.d.ts +76 -0
  9. package/dist/contracts/organizationRuntimeContract.d.ts.map +1 -1
  10. package/dist/contracts/organizationRuntimeContract.js +2 -0
  11. package/dist/contracts/runtimeContractManifest.d.ts +222 -15
  12. package/dist/contracts/runtimeContractManifest.d.ts.map +1 -1
  13. package/dist/contracts/runtimeContractManifest.js +35 -10
  14. package/dist/contracts/shellEnvironmentPolicy.d.ts +3 -0
  15. package/dist/contracts/shellEnvironmentPolicy.d.ts.map +1 -0
  16. package/dist/contracts/shellEnvironmentPolicy.js +5 -0
  17. package/dist/contracts/wakeMomentContract.d.ts +61 -0
  18. package/dist/contracts/wakeMomentContract.d.ts.map +1 -0
  19. package/dist/contracts/wakeMomentContract.js +27 -0
  20. package/dist/core/types.d.ts +2 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/examples/jungianPlayAgent.d.ts +1 -0
  23. package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
  24. package/dist/examples/jungianPlayAgent.js +6 -2
  25. package/dist/observability/causalEvents.d.ts +3 -3
  26. package/dist/observability/causalEvents.d.ts.map +1 -1
  27. package/dist/observability/causalEvents.js +4 -4
  28. package/dist/pi/cliEngineSpawn.d.ts +2 -2
  29. package/dist/pi/cliEngineSpawn.d.ts.map +1 -1
  30. package/dist/pi/cliEngineSpawn.js +7 -2
  31. package/dist/pi/cliEnvironment.d.ts.map +1 -1
  32. package/dist/pi/cliEnvironment.js +2 -0
  33. package/dist/pi/cliMcpRegistration.d.ts +1 -1
  34. package/dist/pi/cliMcpRegistration.d.ts.map +1 -1
  35. package/dist/pi/cliMcpRegistration.js +1 -1
  36. package/dist/pi/cliSession.d.ts +2 -0
  37. package/dist/pi/cliSession.d.ts.map +1 -1
  38. package/dist/pi/cliSession.js +3 -80
  39. package/dist/pi/cliSessionMcp.d.ts +9 -0
  40. package/dist/pi/cliSessionMcp.d.ts.map +1 -0
  41. package/dist/pi/cliSessionMcp.js +80 -0
  42. package/dist/pi/engineTaskClock.d.ts +9 -0
  43. package/dist/pi/engineTaskClock.d.ts.map +1 -0
  44. package/dist/pi/engineTaskClock.js +28 -0
  45. package/dist/pi/grokHomeMcpRegistration.d.ts.map +1 -1
  46. package/dist/pi/grokHomeMcpRegistration.js +2 -1
  47. package/dist/pi/grokSandbox.js +1 -1
  48. package/dist/pi/memoryClock.d.ts +25 -0
  49. package/dist/pi/memoryClock.d.ts.map +1 -0
  50. package/dist/pi/memoryClock.js +18 -0
  51. package/dist/pi/piAgentHandle.d.ts +3 -1
  52. package/dist/pi/piAgentHandle.d.ts.map +1 -1
  53. package/dist/pi/piAgentHandle.js +5 -2
  54. package/dist/pi/piHarness.d.ts +8 -2
  55. package/dist/pi/piHarness.d.ts.map +1 -1
  56. package/dist/pi/piHarness.js +28 -12
  57. package/dist/pi/taskClockProcess.d.ts +6 -0
  58. package/dist/pi/taskClockProcess.d.ts.map +1 -0
  59. package/dist/pi/taskClockProcess.js +8 -0
  60. package/dist/pi/turnCausal.d.ts +4 -2
  61. package/dist/pi/turnCausal.d.ts.map +1 -1
  62. package/dist/pi/turnCausal.js +4 -4
  63. package/dist/pi/wakeAcceptance.d.ts +1 -1
  64. package/dist/pi/wakeAcceptance.d.ts.map +1 -1
  65. package/dist/pi/wakeAcceptance.js +2 -2
  66. package/dist/runtime/agentSkillsDirectory.d.ts +25 -0
  67. package/dist/runtime/agentSkillsDirectory.d.ts.map +1 -0
  68. package/dist/runtime/agentSkillsDirectory.js +101 -0
  69. package/dist/runtime/agySubscriptionBootstrap.js +1 -1
  70. package/dist/runtime/agySubscriptionRealm.js +1 -1
  71. package/dist/runtime/attention.d.ts +5 -0
  72. package/dist/runtime/attention.d.ts.map +1 -1
  73. package/dist/runtime/attention.js +14 -1
  74. package/dist/runtime/attentionDispatcher.d.ts +14 -2
  75. package/dist/runtime/attentionDispatcher.d.ts.map +1 -1
  76. package/dist/runtime/attentionDispatcher.js +127 -33
  77. package/dist/runtime/cli.js +5 -0
  78. package/dist/runtime/contract-manifest.json +1 -1
  79. package/dist/runtime/contract-manifest.sha256 +1 -1
  80. package/dist/runtime/engineBrokerCapabilities.d.ts +10 -0
  81. package/dist/runtime/engineBrokerCapabilities.d.ts.map +1 -1
  82. package/dist/runtime/engineBrokerCapabilities.js +10 -0
  83. package/dist/runtime/engineBrokerControlClient.d.ts +12 -1
  84. package/dist/runtime/engineBrokerControlClient.d.ts.map +1 -1
  85. package/dist/runtime/engineBrokerControlClient.js +139 -43
  86. package/dist/runtime/engineBrokerCredentialJournal.d.ts +2 -0
  87. package/dist/runtime/engineBrokerCredentialJournal.d.ts.map +1 -1
  88. package/dist/runtime/engineBrokerCredentialJournal.js +9 -0
  89. package/dist/runtime/engineBrokerInferenceProtocol.d.ts +3 -2
  90. package/dist/runtime/engineBrokerInferenceProtocol.d.ts.map +1 -1
  91. package/dist/runtime/engineBrokerInferenceProtocol.js +4 -4
  92. package/dist/runtime/engineBrokerMcpCallLog.d.ts +11 -0
  93. package/dist/runtime/engineBrokerMcpCallLog.d.ts.map +1 -1
  94. package/dist/runtime/engineBrokerMcpCallLog.js +13 -1
  95. package/dist/runtime/engineBrokerMcpFacade.d.ts +5 -2
  96. package/dist/runtime/engineBrokerMcpFacade.d.ts.map +1 -1
  97. package/dist/runtime/engineBrokerMcpFacade.js +5 -2
  98. package/dist/runtime/engineBrokerNativeClient.js +2 -2
  99. package/dist/runtime/engineBrokerProtocol.d.ts +42 -10
  100. package/dist/runtime/engineBrokerProtocol.d.ts.map +1 -1
  101. package/dist/runtime/engineBrokerProtocol.js +97 -14
  102. package/dist/runtime/engineBrokerSealLedger.d.ts.map +1 -1
  103. package/dist/runtime/engineBrokerSealLedger.js +9 -0
  104. package/dist/runtime/engineBrokerService.d.ts +8 -2
  105. package/dist/runtime/engineBrokerService.d.ts.map +1 -1
  106. package/dist/runtime/engineBrokerService.js +95 -37
  107. package/dist/runtime/engineBrokerServiceCli.js +1 -1
  108. package/dist/runtime/engineBrokerServiceConfig.d.ts +15 -2
  109. package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
  110. package/dist/runtime/engineBrokerServiceConfig.js +53 -11
  111. package/dist/runtime/engineBrokerTurnRegistry.d.ts +1 -1
  112. package/dist/runtime/engineBrokerTurnRegistry.d.ts.map +1 -1
  113. package/dist/runtime/engineBrokerTurnRegistry.js +7 -6
  114. package/dist/runtime/engineDispatcher.d.ts +2 -0
  115. package/dist/runtime/engineDispatcher.d.ts.map +1 -1
  116. package/dist/runtime/engineDispatcher.js +14 -2
  117. package/dist/runtime/engineReadiness.js +1 -1
  118. package/dist/runtime/grokBrokerCredentialAuthority.d.ts +1 -0
  119. package/dist/runtime/grokBrokerCredentialAuthority.d.ts.map +1 -1
  120. package/dist/runtime/grokBrokerCredentialAuthority.js +48 -22
  121. package/dist/runtime/grokBrokerCredentialReader.d.ts +1 -0
  122. package/dist/runtime/grokBrokerCredentialReader.d.ts.map +1 -1
  123. package/dist/runtime/grokBrokerCredentialReader.js +2 -1
  124. package/dist/runtime/grokBrokerProjection.d.ts +26 -2
  125. package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
  126. package/dist/runtime/grokBrokerProjection.js +18 -7
  127. package/dist/runtime/grokBrokerProviderClock.d.ts +26 -0
  128. package/dist/runtime/grokBrokerProviderClock.d.ts.map +1 -0
  129. package/dist/runtime/grokBrokerProviderClock.js +133 -0
  130. package/dist/runtime/grokBrokerProxy.d.ts +17 -3
  131. package/dist/runtime/grokBrokerProxy.d.ts.map +1 -1
  132. package/dist/runtime/grokBrokerProxy.js +73 -9
  133. package/dist/runtime/grokBrokerProxyJson.d.ts +10 -0
  134. package/dist/runtime/grokBrokerProxyJson.d.ts.map +1 -0
  135. package/dist/runtime/grokBrokerProxyJson.js +31 -0
  136. package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -1
  137. package/dist/runtime/grokBrokerProxyRequest.js +17 -6
  138. package/dist/runtime/grokBrokerRealmLease.js +1 -1
  139. package/dist/runtime/grokBrokerRefresh.js +1 -1
  140. package/dist/runtime/grokBrokerRunBudget.d.ts +20 -0
  141. package/dist/runtime/grokBrokerRunBudget.d.ts.map +1 -0
  142. package/dist/runtime/grokBrokerRunBudget.js +85 -0
  143. package/dist/runtime/grokBrokerSlot.d.ts +6 -0
  144. package/dist/runtime/grokBrokerSlot.d.ts.map +1 -0
  145. package/dist/runtime/grokBrokerSlot.js +21 -0
  146. package/dist/runtime/grokBrokerTaskClock.d.ts +32 -0
  147. package/dist/runtime/grokBrokerTaskClock.d.ts.map +1 -0
  148. package/dist/runtime/grokBrokerTaskClock.js +120 -0
  149. package/dist/runtime/grokBrokerTurnMeter.d.ts +55 -3
  150. package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -1
  151. package/dist/runtime/grokBrokerTurnMeter.js +90 -4
  152. package/dist/runtime/grokBrokerWorkerConfig.d.ts +5 -2
  153. package/dist/runtime/grokBrokerWorkerConfig.d.ts.map +1 -1
  154. package/dist/runtime/grokBrokerWorkerConfig.js +22 -3
  155. package/dist/runtime/grokEngineBroker.d.ts +43 -3
  156. package/dist/runtime/grokEngineBroker.d.ts.map +1 -1
  157. package/dist/runtime/grokEngineBroker.js +159 -71
  158. package/dist/runtime/grokEngineBrokerTurn.d.ts +32 -8
  159. package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -1
  160. package/dist/runtime/grokEngineBrokerTurn.js +204 -21
  161. package/dist/runtime/grokEngineBrokerTurnEnd.d.ts +88 -0
  162. package/dist/runtime/grokEngineBrokerTurnEnd.d.ts.map +1 -0
  163. package/dist/runtime/grokEngineBrokerTurnEnd.js +148 -0
  164. package/dist/runtime/grokInferenceProxy.d.ts.map +1 -1
  165. package/dist/runtime/grokInferenceProxy.js +3 -0
  166. package/dist/runtime/grokSubscriptionRealm.js +1 -1
  167. package/dist/runtime/grokWorkerAttestation.d.ts +12 -5
  168. package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
  169. package/dist/runtime/grokWorkerAttestation.js +5 -3
  170. package/dist/runtime/grokWorkerHomeAttestation.d.ts +51 -5
  171. package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
  172. package/dist/runtime/grokWorkerHomeAttestation.js +103 -8
  173. package/dist/runtime/grokWorkerSkills.d.ts +86 -0
  174. package/dist/runtime/grokWorkerSkills.d.ts.map +1 -0
  175. package/dist/runtime/grokWorkerSkills.js +135 -0
  176. package/dist/runtime/index.d.ts +10 -2
  177. package/dist/runtime/index.d.ts.map +1 -1
  178. package/dist/runtime/index.js +8 -1
  179. package/dist/runtime/native/daimon-engine-broker +0 -0
  180. package/dist/runtime/organizationRuntime.d.ts +3 -0
  181. package/dist/runtime/organizationRuntime.d.ts.map +1 -1
  182. package/dist/runtime/organizationRuntimeAdmission.d.ts +6 -0
  183. package/dist/runtime/organizationRuntimeAdmission.d.ts.map +1 -0
  184. package/dist/runtime/organizationRuntimeAdmission.js +22 -0
  185. package/dist/runtime/organizationRuntimeControl.d.ts +9 -0
  186. package/dist/runtime/organizationRuntimeControl.d.ts.map +1 -1
  187. package/dist/runtime/organizationRuntimeControl.js +72 -24
  188. package/dist/runtime/organizationRuntimeHost.d.ts.map +1 -1
  189. package/dist/runtime/organizationRuntimeHost.js +13 -4
  190. package/dist/runtime/organizationRuntimeParsing.d.ts.map +1 -1
  191. package/dist/runtime/organizationRuntimeParsing.js +4 -1
  192. package/dist/runtime/organizationRuntimeReadiness.d.ts.map +1 -1
  193. package/dist/runtime/organizationRuntimeReadiness.js +8 -0
  194. package/dist/runtime/organizationRuntimeRecording.d.ts +7 -0
  195. package/dist/runtime/organizationRuntimeRecording.d.ts.map +1 -0
  196. package/dist/runtime/organizationRuntimeRecording.js +58 -0
  197. package/dist/runtime/physicalReadiness.d.ts +12 -0
  198. package/dist/runtime/physicalReadiness.d.ts.map +1 -1
  199. package/dist/runtime/physicalReadiness.js +43 -9
  200. package/dist/runtime/productionAgentTools.d.ts.map +1 -1
  201. package/dist/runtime/productionAgentTools.js +13 -5
  202. package/dist/runtime/relocateRuntimeConfig.d.ts +14 -0
  203. package/dist/runtime/relocateRuntimeConfig.d.ts.map +1 -0
  204. package/dist/runtime/relocateRuntimeConfig.js +38 -0
  205. package/dist/runtime/schedule.d.ts.map +1 -1
  206. package/dist/runtime/schedule.js +12 -7
  207. package/dist/runtime/storeCoordination.d.ts +10 -0
  208. package/dist/runtime/storeCoordination.d.ts.map +1 -1
  209. package/dist/runtime/storeCoordination.js +23 -0
  210. package/dist/runtime/taskClock.d.ts +42 -0
  211. package/dist/runtime/taskClock.d.ts.map +1 -0
  212. package/dist/runtime/taskClock.js +102 -0
  213. package/dist/runtime/taskClockProcess.d.ts +17 -0
  214. package/dist/runtime/taskClockProcess.d.ts.map +1 -0
  215. package/dist/runtime/taskClockProcess.js +58 -0
  216. package/dist/runtime/turnRequestLedger.d.ts +0 -5
  217. package/dist/runtime/turnRequestLedger.d.ts.map +1 -1
  218. package/dist/runtime/turnRequestLedger.js +23 -21
  219. package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts +34 -0
  220. package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts.map +1 -0
  221. package/dist/runtime/wakeAcceptanceClaimDiagnostics.js +79 -0
  222. package/dist/runtime/wakeAcceptanceIndex.d.ts +55 -0
  223. package/dist/runtime/wakeAcceptanceIndex.d.ts.map +1 -0
  224. package/dist/runtime/wakeAcceptanceIndex.js +158 -0
  225. package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts +9 -1
  226. package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts.map +1 -1
  227. package/dist/runtime/wakeAcceptanceProcessIdentity.js +5 -1
  228. package/dist/runtime/wakeAcceptanceRecord.d.ts +2 -0
  229. package/dist/runtime/wakeAcceptanceRecord.d.ts.map +1 -1
  230. package/dist/runtime/wakeAcceptanceRecord.js +4 -2
  231. package/dist/runtime/wakeAcceptanceRetention.d.ts +25 -1
  232. package/dist/runtime/wakeAcceptanceRetention.d.ts.map +1 -1
  233. package/dist/runtime/wakeAcceptanceRetention.js +28 -2
  234. package/dist/runtime/wakeAcceptanceStore.d.ts +8 -8
  235. package/dist/runtime/wakeAcceptanceStore.d.ts.map +1 -1
  236. package/dist/runtime/wakeAcceptanceStore.js +66 -63
  237. package/dist/runtime/wakeFuse.d.ts +58 -3
  238. package/dist/runtime/wakeFuse.d.ts.map +1 -1
  239. package/dist/runtime/wakeFuse.js +106 -7
  240. package/dist/runtime/wakeMomentFs.d.ts +27 -0
  241. package/dist/runtime/wakeMomentFs.d.ts.map +1 -0
  242. package/dist/runtime/wakeMomentFs.js +223 -0
  243. package/dist/runtime/wakeMomentHistory.d.ts +26 -0
  244. package/dist/runtime/wakeMomentHistory.d.ts.map +1 -0
  245. package/dist/runtime/wakeMomentHistory.js +109 -0
  246. package/dist/runtime/wakeMomentIo.d.ts +22 -0
  247. package/dist/runtime/wakeMomentIo.d.ts.map +1 -0
  248. package/dist/runtime/wakeMomentIo.js +114 -0
  249. package/dist/runtime/wakeMomentRecorder.d.ts +33 -0
  250. package/dist/runtime/wakeMomentRecorder.d.ts.map +1 -0
  251. package/dist/runtime/wakeMomentRecorder.js +81 -0
  252. package/dist/runtime/wakeMomentRetention.d.ts +7 -0
  253. package/dist/runtime/wakeMomentRetention.d.ts.map +1 -0
  254. package/dist/runtime/wakeMomentRetention.js +143 -0
  255. package/dist/runtime/wakeMomentRows.d.ts +8 -0
  256. package/dist/runtime/wakeMomentRows.d.ts.map +1 -0
  257. package/dist/runtime/wakeMomentRows.js +66 -0
  258. package/dist/runtime/wakeMomentSnapshot.d.ts +56 -0
  259. package/dist/runtime/wakeMomentSnapshot.d.ts.map +1 -0
  260. package/dist/runtime/wakeMomentSnapshot.js +248 -0
  261. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { WakeExecutionClaimLostError, readExpectedClaim, waitForClaimTransitionLock } from "./wakeAcceptanceClaimDiagnostics.js";
1
2
  import { sameNamespace, currentProcessIdentity, processIsAlive, WakeTransitionLockBlockedError } from "./wakeAcceptanceProcessIdentity.js";
2
3
  import { constants } from "node:fs";
3
4
  import { link, lstat, open, readFile, readdir, realpath, rename, unlink } from "node:fs/promises";
@@ -6,8 +7,9 @@ import { createHash, randomUUID } from "node:crypto";
6
7
  import { MAX_WAKE_ACCEPTANCE_RECORD_BYTES, sanitizeWakeCompletionText, wakeAcceptanceDigest } from "./wakeAcceptanceTypes.js";
7
8
  import { sanitizeExecutionError, parseStoredWakeAcceptance, publicStatus } from "./wakeAcceptanceRecord.js";
8
9
  import { assertOfflineReconciliationLeaseAvailable } from "./wakeAcceptanceReconciliation.js";
9
- import { acquireHostRegistration, releaseHostRegistration } from "./storeCoordination.js";
10
- import { MAX_WAKE_ACCEPTANCE_RECORDS, terminalFilesToCompact } from "./wakeAcceptanceRetention.js";
10
+ import { acquireHostRegistration, pruneDeadHostRegistrations, releaseHostRegistration } from "./storeCoordination.js";
11
+ import { WakeAcceptanceIndex } from "./wakeAcceptanceIndex.js";
12
+ import { MAX_WAKE_ACCEPTANCE_RECORDS, WAKE_ACCEPTANCE_COMPACTION_THRESHOLD, staleQueuedFilesToStop, terminalFilesToCompact } from "./wakeAcceptanceRetention.js";
11
13
  const DEFAULT_CLAIM_TTL_MS = 240_000;
12
14
  /** Durable, private idempotency authority; callers must pre-create its 0700 root. */
13
15
  export class WakeAcceptanceStore {
@@ -21,7 +23,9 @@ export class WakeAcceptanceStore {
21
23
  ownerLiveness;
22
24
  afterFinalLockAssertion;
23
25
  mutations = Promise.resolve();
24
- acceptanceFiles = new Map();
26
+ index;
27
+ onRecordRead;
28
+ claimTransitionLockTiming;
25
29
  constructor(root, directory, identity, registration, claimTtlMs, now, owner, ownerLiveness, afterFinalLockAssertion) {
26
30
  this.root = root;
27
31
  this.directory = directory;
@@ -32,6 +36,8 @@ export class WakeAcceptanceStore {
32
36
  this.owner = owner;
33
37
  this.ownerLiveness = ownerLiveness;
34
38
  this.afterFinalLockAssertion = afterFinalLockAssertion;
39
+ this.index = new WakeAcceptanceIndex(root, { directoryStat: async () => await this.directory.stat({ bigint: true }), files: async () => await this.files(), readId: async (file) => (await this.read(file)).acceptance_id, fileStat: async (file) => await lstat(file, { bigint: true }).catch((error) => { if (error.code === "ENOENT")
40
+ return undefined; throw error; }) }, () => BigInt(this.now()) * 1000000n);
35
41
  }
36
42
  static async open(root, options = {}) {
37
43
  const claimTtlMs = options.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS;
@@ -53,23 +59,27 @@ export class WakeAcceptanceStore {
53
59
  if (!same(identity(before), identity(after)))
54
60
  throw new Error("wake acceptance store changed during validation");
55
61
  const registration = await acquireHostRegistration(real, directory, owner);
56
- return new WakeAcceptanceStore(real, directory, identity(before), registration, claimTtlMs, options.nowForTest ?? Date.now, owner, options.ownerLiveness ?? processIsAlive, options.afterFinalLockAssertion);
62
+ await pruneDeadHostRegistrations(real, directory, registration, options.ownerLiveness ?? processIsAlive);
63
+ const store = new WakeAcceptanceStore(real, directory, identity(before), registration, claimTtlMs, options.nowForTest ?? Date.now, owner, options.ownerLiveness ?? processIsAlive, options.afterFinalLockAssertion);
64
+ store.claimTransitionLockTiming = options.claimTransitionLockTimingForTest;
65
+ store.onRecordRead = options.onRecordReadForTest;
66
+ return store;
57
67
  }
58
68
  catch (error) {
59
69
  await directory.close().catch(() => undefined);
60
70
  throw error;
61
71
  }
62
72
  }
63
- accept(request) {
64
- return this.serialize(async () => await this.acceptNow(request));
73
+ accept(request, nativeSchedule = false) {
74
+ return this.serialize(async () => await this.acceptNow(request, nativeSchedule));
65
75
  }
66
- async acceptNow(request) {
76
+ async acceptNow(request, nativeSchedule) {
67
77
  await this.verify();
68
78
  const request_digest = wakeAcceptanceDigest(request);
69
79
  const target = this.fileFor(request.agent_id, request.delivery_id);
70
80
  const existing = await this.readOptional(target);
71
81
  if (existing !== undefined) {
72
- if (existing.request_digest !== request_digest)
82
+ if (existing.request_digest !== request_digest || existing.native_schedule !== (nativeSchedule ? true : undefined))
73
83
  throw new WakeAcceptanceConflictError();
74
84
  return { record: existing, created: false };
75
85
  }
@@ -77,7 +87,7 @@ export class WakeAcceptanceStore {
77
87
  if ((await this.files()).length >= MAX_WAKE_ACCEPTANCE_RECORDS)
78
88
  throw new WakeInboxFullError();
79
89
  const now = new Date().toISOString();
80
- const record = { acceptance_id: randomUUID(), agent_id: request.agent_id, delivery_id: request.delivery_id, request_digest, event: request.event, state: "accepted", accepted_at: now, updated_at: now };
90
+ const record = { acceptance_id: randomUUID(), agent_id: request.agent_id, delivery_id: request.delivery_id, request_digest, event: request.event, state: "accepted", accepted_at: now, updated_at: now, ...(nativeSchedule ? { native_schedule: true } : {}) };
81
91
  const temporary = path.join(this.root, `.pending-${randomUUID()}`);
82
92
  try {
83
93
  await this.writeNew(temporary, record);
@@ -88,12 +98,12 @@ export class WakeAcceptanceStore {
88
98
  if (error.code !== "EEXIST")
89
99
  throw error;
90
100
  const winner = await this.read(target);
91
- if (winner.request_digest !== request_digest)
101
+ if (winner.request_digest !== request_digest || winner.native_schedule !== (nativeSchedule ? true : undefined))
92
102
  throw new WakeAcceptanceConflictError();
93
103
  return { record: winner, created: false };
94
104
  }
95
105
  await this.directory.sync();
96
- this.acceptanceFiles.set(record.acceptance_id, target);
106
+ this.index.set(record.acceptance_id, target);
97
107
  return { record, created: true };
98
108
  }
99
109
  finally {
@@ -123,7 +133,7 @@ export class WakeAcceptanceStore {
123
133
  ...(includeExecutionErrors && record.execution_error !== undefined ? { execution_error: record.execution_error } : {}) };
124
134
  });
125
135
  }
126
- async recoverable(agentIds) {
136
+ async recoverable(agentIds, onStale) {
127
137
  const result = [];
128
138
  for (const file of await this.files()) {
129
139
  const record = await this.read(path.join(this.root, file));
@@ -131,7 +141,12 @@ export class WakeAcceptanceStore {
131
141
  throw new Error("wake acceptance store contains an unknown agent authority");
132
142
  if (record.state === "accepted" || record.state === "running") {
133
143
  const claim = await this.readClaimOptional(this.claimFor(record));
134
- result.push(!record.deferred && record.execution_id === undefined && claim?.acceptance_ids?.includes(record.acceptance_id) && claim.execution_id !== undefined ? { ...record, execution_id: claim.execution_id } : record);
144
+ // A long-parked delivery is no longer offered: it would head every batch ahead of today's mail. Compaction stops it.
145
+ if (staleQueuedFilesToStop([{ file, state: record.state, updatedAt: record.updated_at, acceptedAt: record.accepted_at, acceptanceId: record.acceptance_id, claimed: claim !== undefined && !expired(claim, this.now()) }], this.now()).length) {
146
+ onStale?.(record);
147
+ continue;
148
+ }
149
+ result.push(!record.deferred && record.execution_id === undefined && record.execution_error === undefined && claim?.acceptance_ids?.includes(record.acceptance_id) && claim.execution_id !== undefined ? { ...record, execution_id: claim.execution_id } : record);
135
150
  }
136
151
  }
137
152
  return result.sort((left, right) => left.accepted_at.localeCompare(right.accepted_at) || left.acceptance_id.localeCompare(right.acceptance_id));
@@ -140,13 +155,14 @@ export class WakeAcceptanceStore {
140
155
  return this.serialize(async () => await this.transitionNow(acceptanceId, state, code));
141
156
  }
142
157
  /** Fuse race seam: never stops a record that was claimed after enumeration. */
143
- transitionAcceptedToStopped(acceptanceId) {
158
+ transitionAcceptedToStopped(acceptanceId, code = "host_stopping") {
144
159
  return this.serialize(async () => {
145
160
  const target = await this.pathForAcceptanceId(acceptanceId);
146
161
  const prior = await this.read(target);
147
- if (prior.state !== "accepted")
162
+ const claim = prior.state === "accepted" ? await this.readClaimOptional(this.claimFor(prior)) : undefined;
163
+ if (prior.state !== "accepted" || (claim !== undefined && !expired(claim, this.now())))
148
164
  return prior;
149
- const record = { ...prior, state: "stopped", code: "host_stopping", updated_at: new Date().toISOString() };
165
+ const record = { ...prior, state: "stopped", code, updated_at: new Date().toISOString() };
150
166
  await this.replace(target, record);
151
167
  return record;
152
168
  });
@@ -216,13 +232,9 @@ export class WakeAcceptanceStore {
216
232
  const record = await this.findByAcceptanceId(acceptanceId);
217
233
  if (record === undefined)
218
234
  throw new Error("wake acceptance receipt is unavailable");
219
- const lock = await this.acquireTransitionLock(record, claim.owner_id);
220
- if (lock === undefined)
221
- throw new WakeExecutionClaimLostError();
235
+ const lock = await waitForClaimTransitionLock(() => this.acquireTransitionLock(record, claim.owner_id), () => this.readLockOptional(this.lockFor(record)), acceptanceId, claim, "renew", this.claimTransitionLockTiming);
222
236
  try {
223
- const current = await this.readClaim(this.claimFor(record));
224
- if (!(current.acceptance_id === acceptanceId || current.acceptance_ids?.includes(acceptanceId)) || current.acceptance_id !== claim.acceptance_id || current.owner_id !== claim.owner_id || current.generation !== claim.generation || current.expires_at !== claim.expires_at || expired(current, this.now()))
225
- throw new WakeExecutionClaimLostError();
237
+ const current = await readExpectedClaim(() => this.readClaim(this.claimFor(record)), acceptanceId, claim, "renew", this.now, lock);
226
238
  const renewed = { ...current, expires_at: new Date(this.now() + this.claimTtlMs).toISOString() };
227
239
  await this.replaceValue(this.claimFor(record), renewed);
228
240
  return renewed;
@@ -240,24 +252,22 @@ export class WakeAcceptanceStore {
240
252
  const initial = await this.findByAcceptanceId(acceptanceId);
241
253
  if (initial === undefined)
242
254
  throw new Error("wake acceptance receipt is unavailable");
243
- const lock = await this.acquireTransitionLock(initial, claim.owner_id);
244
- if (lock === undefined)
245
- throw new WakeExecutionClaimLostError();
255
+ const lock = await waitForClaimTransitionLock(() => this.acquireTransitionLock(initial, claim.owner_id), () => this.readLockOptional(this.lockFor(initial)), acceptanceId, claim, `transition:${state}`, this.claimTransitionLockTiming);
246
256
  try {
247
- let record = await this.transitionNowClaimed(acceptanceId, claim);
257
+ let record = await this.transitionNowClaimed(acceptanceId, claim, `transition:${state}:initial`, lock);
248
258
  if (isTerminal(record.state))
249
259
  return record;
250
260
  if (state !== "running" && record.claim_generation !== claim.generation)
251
- throw new WakeExecutionClaimLostError();
252
- await this.assertTransitionLock(record, lock);
253
- record = await this.transitionNowClaimed(acceptanceId, claim);
261
+ throw new WakeExecutionClaimLostError({ step: `transition:${state}`, assertion: "record_claim_generation", acceptance_id: acceptanceId, expected: claim.generation, current: record.claim_generation ?? null, lock });
262
+ await this.assertTransitionLock(record, lock, `transition:${state}`);
263
+ record = await this.transitionNowClaimed(acceptanceId, claim, `transition:${state}:final`, lock);
254
264
  if (isTerminal(record.state))
255
265
  return record;
256
266
  if (state !== "running" && record.claim_generation !== claim.generation)
257
- throw new WakeExecutionClaimLostError();
258
- await this.assertTransitionLock(record, lock);
267
+ throw new WakeExecutionClaimLostError({ step: `transition:${state}`, assertion: "record_claim_generation", acceptance_id: acceptanceId, expected: claim.generation, current: record.claim_generation ?? null, lock });
268
+ await this.assertTransitionLock(record, lock, `transition:${state}`);
259
269
  await this.afterFinalLockAssertion?.();
260
- await this.assertTransitionLock(record, lock);
270
+ await this.assertTransitionLock(record, lock, `transition:${state}:before_write`);
261
271
  const target = this.fileFor(record.agent_id, record.delivery_id);
262
272
  // The code explains the transition that produced the CURRENT state, so a
263
273
  // transition that names none clears it. While codes existed only on terminal
@@ -285,14 +295,12 @@ export class WakeAcceptanceStore {
285
295
  return this.serialize(async () => {
286
296
  const record = await this.findByAcceptanceId(claim.acceptance_id);
287
297
  if (!record)
288
- throw new WakeExecutionClaimLostError();
289
- const lock = await this.acquireTransitionLock(record, claim.owner_id);
290
- if (!lock)
291
- throw new WakeExecutionClaimLostError();
298
+ throw new WakeExecutionClaimLostError({ step: "release", assertion: "record_missing", acceptance_id: claim.acceptance_id, expected: claim, current: null });
299
+ const lock = await waitForClaimTransitionLock(() => this.acquireTransitionLock(record, claim.owner_id), () => this.readLockOptional(this.lockFor(record)), claim.acceptance_id, claim, "release", this.claimTransitionLockTiming);
292
300
  try {
293
301
  const current = await this.readClaimOptional(this.claimFor(record));
294
302
  if (current?.generation !== claim.generation || current.owner_id !== claim.owner_id)
295
- throw new WakeExecutionClaimLostError();
303
+ throw new WakeExecutionClaimLostError({ step: "release", assertion: "claim_identity", acceptance_id: claim.acceptance_id, expected: claim, current: current ?? null, lock });
296
304
  await unlink(this.claimFor(record));
297
305
  await this.directory.sync();
298
306
  }
@@ -329,16 +337,8 @@ export class WakeAcceptanceStore {
329
337
  async findByAcceptanceId(acceptanceId) {
330
338
  if (!uuid(acceptanceId))
331
339
  return undefined;
332
- const known = this.acceptanceFiles.get(acceptanceId);
333
- if (known !== undefined)
334
- return await this.readOptional(known);
335
- for (const file of await this.files()) {
336
- const record = await this.read(path.join(this.root, file));
337
- this.acceptanceFiles.set(record.acceptance_id, path.join(this.root, file));
338
- if (record.acceptance_id === acceptanceId)
339
- return record;
340
- }
341
- return undefined;
340
+ await this.verify();
341
+ return await this.index.find(acceptanceId, async (file) => await this.readOptional(file));
342
342
  }
343
343
  async pathForAcceptanceId(acceptanceId) {
344
344
  const record = await this.findByAcceptanceId(acceptanceId);
@@ -352,7 +352,7 @@ export class WakeAcceptanceStore {
352
352
  const target = this.lockFor(record);
353
353
  const current = await this.readLockOptional(target);
354
354
  if (current !== undefined && !sameNamespace(current, this.owner))
355
- throw new WakeTransitionLockBlockedError();
355
+ throw new WakeTransitionLockBlockedError({ step: "acquire_transition_lock", acceptance_id: record.acceptance_id, expected: this.owner, current });
356
356
  if (current !== undefined && await this.ownerLiveness(current))
357
357
  return undefined;
358
358
  if (current !== undefined) {
@@ -378,10 +378,10 @@ export class WakeAcceptanceStore {
378
378
  await unlink(temporary).catch(() => undefined);
379
379
  }
380
380
  }
381
- async assertTransitionLock(record, lock) {
382
- const current = await this.readLock(this.lockFor(record)).catch(() => { throw new WakeExecutionClaimLostError(); });
381
+ async assertTransitionLock(record, lock, step) {
382
+ const current = await this.readLock(this.lockFor(record)).catch((cause) => { throw new WakeExecutionClaimLostError({ step, assertion: "transition_lock_read", acceptance_id: record.acceptance_id, expected: lock, current: null }, cause); });
383
383
  if (current.owner_id !== lock.owner_id || current.generation !== lock.generation || current.pid !== lock.pid || current.process_start !== lock.process_start || current.boot_id !== lock.boot_id || !sameNamespace(current, lock))
384
- throw new WakeExecutionClaimLostError();
384
+ throw new WakeExecutionClaimLostError({ step, assertion: "transition_lock_identity", acceptance_id: record.acceptance_id, expected: lock, current });
385
385
  }
386
386
  async releaseTransitionLock(record, lock) {
387
387
  const target = this.lockFor(record);
@@ -399,7 +399,7 @@ export class WakeAcceptanceStore {
399
399
  const entries = await readdir(this.root);
400
400
  const files = entries.filter((entry) => /^[a-f0-9]{64}\.json$/.test(entry));
401
401
  if (files.length > MAX_WAKE_ACCEPTANCE_RECORDS || entries.length > MAX_WAKE_ACCEPTANCE_RECORDS + 128)
402
- throw new Error("wake acceptance store exceeds its bounded record limit");
402
+ throw new WakeInboxFullError("wake acceptance store exceeds its bounded record limit");
403
403
  return files;
404
404
  }
405
405
  async claimFiles() { return (await readdir(this.root)).filter((entry) => /^[a-f0-9]{64}\.agent-claim$/.test(entry)); }
@@ -415,6 +415,7 @@ export class WakeAcceptanceStore {
415
415
  const entry = await lstat(file);
416
416
  if (!entry.isFile() || entry.isSymbolicLink() || entry.uid !== process.getuid?.() || (entry.mode & 0o777) !== 0o600 || entry.size > MAX_WAKE_ACCEPTANCE_RECORD_BYTES)
417
417
  throw new Error("wake acceptance record is unsafe");
418
+ this.onRecordRead?.(file);
418
419
  const bytes = await readFile(file);
419
420
  if (bytes.length > MAX_WAKE_ACCEPTANCE_RECORD_BYTES)
420
421
  throw new Error("wake acceptance record exceeds its bound");
@@ -471,25 +472,29 @@ export class WakeAcceptanceStore {
471
472
  throw new Error("wake transition lock is invalid");
472
473
  return result;
473
474
  }
474
- async transitionNowClaimed(acceptanceId, claim) {
475
+ async transitionNowClaimed(acceptanceId, claim, step, lock) {
475
476
  const record = await this.findByAcceptanceId(acceptanceId);
476
477
  if (record === undefined)
477
478
  throw new Error("wake acceptance receipt is unavailable");
478
- const current = await this.readClaim(this.claimFor(record));
479
- if (!(current.acceptance_id === acceptanceId || current.acceptance_ids?.includes(acceptanceId)) || current.acceptance_id !== claim.acceptance_id || current.owner_id !== claim.owner_id || current.generation !== claim.generation || current.expires_at !== claim.expires_at || expired(current, this.now()))
480
- throw new WakeExecutionClaimLostError();
479
+ await readExpectedClaim(() => this.readClaim(this.claimFor(record)), acceptanceId, claim, step, this.now, lock);
481
480
  return record;
482
481
  }
483
482
  async compactTerminalRecords() {
484
483
  const files = await this.files();
485
- if (files.length < 2_112)
484
+ if (files.length < WAKE_ACCEPTANCE_COMPACTION_THRESHOLD)
486
485
  return;
487
486
  const records = await Promise.all(files.map(async (file) => ({ file, record: await this.read(path.join(this.root, file)) })));
487
+ const candidates = await Promise.all(records.map(async ({ file, record }) => ({ file, state: record.state, updatedAt: record.updated_at, acceptedAt: record.accepted_at, acceptanceId: record.acceptance_id, claimed: await this.readClaimOptional(this.claimFor(record)).then((claim) => claim !== undefined && !expired(claim, this.now())) })));
488
+ for (const file of staleQueuedFilesToStop(candidates, this.now())) {
489
+ const entry = records.find((candidate) => candidate.file === file);
490
+ entry.record = { ...entry.record, state: "stopped", code: "queued_wake_stopped", updated_at: new Date(this.now()).toISOString() };
491
+ await this.replace(path.join(this.root, file), entry.record);
492
+ }
488
493
  for (const file of terminalFilesToCompact(records.map(({ file, record }) => ({ file, state: record.state, updatedAt: record.updated_at, acceptanceId: record.acceptance_id })))) {
489
494
  const record = records.find((candidate) => candidate.file === file)?.record;
490
- if (record !== undefined)
491
- this.acceptanceFiles.delete(record.acceptance_id);
492
495
  await unlink(path.join(this.root, file));
496
+ if (record !== undefined)
497
+ this.index.forget(path.join(this.root, file));
493
498
  }
494
499
  await this.directory.sync();
495
500
  }
@@ -524,14 +529,12 @@ export class WakeAcceptanceStore {
524
529
  }
525
530
  }
526
531
  export class WakeInboxFullError extends Error {
527
- constructor() { super("wake acceptance store has no capacity without deleting active work"); }
532
+ constructor(message = "wake acceptance store has no capacity without deleting active work") { super(message); }
528
533
  }
529
534
  export class WakeAcceptanceConflictError extends Error {
530
535
  constructor() { super("delivery id is already bound to a different request"); }
531
536
  }
532
- export class WakeExecutionClaimLostError extends Error {
533
- constructor() { super("wake execution claim was lost"); }
534
- }
537
+ export { WakeExecutionClaimLostError, WakeTransitionLockContentionError } from "./wakeAcceptanceClaimDiagnostics.js";
535
538
  /** A container boundary requires deployment-authorized offline reconciliation. */
536
539
  export { WakeTransitionLockBlockedError } from "./wakeAcceptanceProcessIdentity.js";
537
540
  export { publicAcceptance } from "./wakeAcceptanceRecord.js";
@@ -1,5 +1,6 @@
1
1
  export declare const WAKE_FUSE_VERSION: "noopolis.daimon.wake-fuse.v1";
2
2
  export declare const WAKE_FUSE_DIRECTORY_ENV: "DAIMON_WAKE_FUSE_DIRECTORY";
3
+ export declare const WAKE_FUSE_EPOCH_ZONE_ENV: "DAIMON_WAKE_FUSE_EPOCH_ZONE";
3
4
  export declare const DEFAULT_WAKE_FUSE_MAX_WAKES = 100;
4
5
  export declare const DEFAULT_WAKE_FUSE_MAX_TOKENS = 5000000;
5
6
  export type WakeFuseTripReason = "wake_ceiling" | "token_ceiling" | "operator_stop" | "ledger_unavailable";
@@ -30,6 +31,26 @@ export type WakeFuseVerdict = Readonly<{
30
31
  state: "paused";
31
32
  reason: "agent_execution_ceiling" | "agent_token_ceiling";
32
33
  }>;
34
+ /**
35
+ * The counting window, when the operator has not named one.
36
+ *
37
+ * WHY THIS IS DERIVED AND NOT CONFIGURED
38
+ * --------------------------------------
39
+ * `DAIMON_WAKE_FUSE_EPOCH` is read from the container's env file, and an env
40
+ * file is applied at container CREATION only. So an operator who wants a daily
41
+ * budget has to rewrite that file and RECREATE THE CONTAINER every day, and
42
+ * the only thing that ever did that was a daily rebuild. The budget was
43
+ * therefore coupled to a deploy: stop deploying daily and the wake ceiling
44
+ * never resets, and the organization stops admitting wakes for good.
45
+ *
46
+ * A date-derived epoch removes the coupling instead of moving it. There is no
47
+ * file to rewrite, no deploy, no restart, and no clock for anybody to forget.
48
+ *
49
+ * An explicitly configured epoch still wins, unchanged: an operator pinning one
50
+ * window is doing it deliberately, and this must not roll it out from under
51
+ * them.
52
+ */
53
+ export declare function derivedEpoch(organizationKey: string, at: Date, zone: string): string;
33
54
  export type WakeFuseOptions = Readonly<{
34
55
  organizationKey: string;
35
56
  environment?: NodeJS.ProcessEnv;
@@ -45,23 +66,57 @@ export type WakeFuseOptions = Readonly<{
45
66
  export declare class WakeFuse {
46
67
  private readonly armed;
47
68
  private readonly directory;
48
- private readonly epoch;
49
- private readonly epochStartedAt;
69
+ private epoch;
70
+ private epochStartedAt;
50
71
  private readonly maxWakes;
51
72
  private readonly maxTokens;
52
- private readonly admissions;
73
+ private admissions;
53
74
  private readonly usageLedgerPath;
54
75
  private readonly now;
76
+ private readonly nextEpoch;
55
77
  private serial;
56
78
  private reason;
57
79
  private tripMarkerMissing;
58
80
  private constructor();
59
81
  static open(options: WakeFuseOptions): Promise<WakeFuse>;
60
82
  admit(agentId: string, deliveryId: string, limits?: WakeBudgetLimits): Promise<WakeFuseVerdict>;
83
+ /**
84
+ * The budget as the caller should act on it NOW — which means after the day
85
+ * has been allowed to roll.
86
+ *
87
+ * This is the gate the scheduler actually consults: `AttentionDispatcher.drain()`
88
+ * returns at `snapshot().state !== "available"` before it ever reaches
89
+ * `admit()`. Rolling only inside `admitNow` therefore never fired for an
90
+ * organization that had hit its ceiling — it stayed paused across midnight
91
+ * and only a process restart could renew the budget. The roll is serialized
92
+ * with admissions so a snapshot and an admission never see two windows.
93
+ */
61
94
  snapshot(agentId: string, limits?: WakeBudgetLimits): Promise<WakeBudgetSnapshot>;
95
+ private snapshotNow;
62
96
  pollOperatorStop(): Promise<WakeFuseTripReason | undefined>;
63
97
  tripped(): WakeFuseTripReason | undefined;
64
98
  close(): Promise<void>;
99
+ /**
100
+ * Moves the counting window when the derived day has changed.
101
+ *
102
+ * Called at admission, which is the only moment the answer can matter: the
103
+ * organization runtime is a long-lived process, so an epoch computed once at
104
+ * open() would be the same epoch a week later.
105
+ *
106
+ * WHAT ROLLS AND WHAT DOES NOT
107
+ * ----------------------------
108
+ * A new window clears a CEILING trip, exactly as selecting a new epoch by
109
+ * hand always did: `readTripMarker` only honours a marker belonging to the
110
+ * current epoch. It does NOT clear an operator stop. `fuse.stop` is checked
111
+ * by existence and is deliberately not epoch-scoped, so a parked newsroom
112
+ * stays parked across midnight. That asymmetry is the whole point: a budget
113
+ * is a thing that renews, an operator stop is a decision that does not.
114
+ *
115
+ * Admissions are rebuilt from the ledger rather than emptied, so a clock that
116
+ * steps BACKWARD into a window that already spent wakes does not hand the
117
+ * organization a fresh budget.
118
+ */
119
+ private rollEpochIfDue;
65
120
  private admitNow;
66
121
  private trip;
67
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wakeFuse.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeFuse.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,iBAAiB,EAAG,8BAAuC,CAAC;AACzE,eAAO,MAAM,uBAAuB,EAAG,4BAAqC,CAAC;AAC7E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,4BAA4B,UAAY,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC/W,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,yBAAyB,GAAG,qBAAqB,CAAA;CAAE,CAAC,CAAC;AACtN,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;GAMG;AACH,qBAAa,QAAQ;IAMjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAbtB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO;WAYM,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC;IAqC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAS7F,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IASrF,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAQjE,OAAO,IAAI,kBAAkB,GAAG,SAAS;IACnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAEd,QAAQ;YAqBR,IAAI;CAiBnB;AAED,wBAAgB,wBAAwB,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7F"}
1
+ {"version":3,"file":"wakeFuse.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeFuse.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,iBAAiB,EAAG,8BAAuC,CAAC;AACzE,eAAO,MAAM,uBAAuB,EAAG,4BAAqC,CAAC;AAC7E,eAAO,MAAM,wBAAwB,EAAG,6BAAsC,CAAC;AAC/E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,4BAA4B,UAAY,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC/W,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,yBAAyB,GAAG,qBAAqB,CAAA;CAAE,CAAC,CAAC;AACtN;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpF;AAED,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;GAMG;AACH,qBAAa,QAAQ;IAMjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAd5B,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO;WAaM,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IASnG;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAS7E,WAAW;IASnB,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAQjE,OAAO,IAAI,kBAAkB,GAAG,SAAS;IACnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAE5B;;;;;;;;;;;;;;;;;;;OAmBG;YACW,cAAc;YAyBd,QAAQ;YAuBR,IAAI;CAiBnB;AAED,wBAAgB,wBAAwB,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7F"}
@@ -5,8 +5,34 @@ import { createHash, randomUUID } from "node:crypto";
5
5
  import { resolveTurnUsageLedgerPath, TURN_USAGE_LEDGER, TURN_USAGE_MAX_IDENTIFIER_CHARS, TURN_USAGE_ROTATE_BYTES } from "./turnUsageLedger.js";
6
6
  export const WAKE_FUSE_VERSION = "noopolis.daimon.wake-fuse.v1";
7
7
  export const WAKE_FUSE_DIRECTORY_ENV = "DAIMON_WAKE_FUSE_DIRECTORY";
8
+ export const WAKE_FUSE_EPOCH_ZONE_ENV = "DAIMON_WAKE_FUSE_EPOCH_ZONE";
8
9
  export const DEFAULT_WAKE_FUSE_MAX_WAKES = 100;
9
10
  export const DEFAULT_WAKE_FUSE_MAX_TOKENS = 5_000_000;
11
+ /**
12
+ * The counting window, when the operator has not named one.
13
+ *
14
+ * WHY THIS IS DERIVED AND NOT CONFIGURED
15
+ * --------------------------------------
16
+ * `DAIMON_WAKE_FUSE_EPOCH` is read from the container's env file, and an env
17
+ * file is applied at container CREATION only. So an operator who wants a daily
18
+ * budget has to rewrite that file and RECREATE THE CONTAINER every day, and
19
+ * the only thing that ever did that was a daily rebuild. The budget was
20
+ * therefore coupled to a deploy: stop deploying daily and the wake ceiling
21
+ * never resets, and the organization stops admitting wakes for good.
22
+ *
23
+ * A date-derived epoch removes the coupling instead of moving it. There is no
24
+ * file to rewrite, no deploy, no restart, and no clock for anybody to forget.
25
+ *
26
+ * An explicitly configured epoch still wins, unchanged: an operator pinning one
27
+ * window is doing it deliberately, and this must not roll it out from under
28
+ * them.
29
+ */
30
+ export function derivedEpoch(organizationKey, at, zone) {
31
+ // `en-CA` renders ISO-8601 (YYYY-MM-DD), which is what makes this sortable
32
+ // and stable; the zone is what makes "a day" mean the operator's day.
33
+ const day = new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(at);
34
+ return `organization-${createHash("sha256").update(organizationKey).digest("hex")}-${day}`;
35
+ }
10
36
  const TRIP_MARKER = "fuse.trip.json";
11
37
  let warnedDisarmed = false;
12
38
  /**
@@ -26,10 +52,11 @@ export class WakeFuse {
26
52
  admissions;
27
53
  usageLedgerPath;
28
54
  now;
55
+ nextEpoch;
29
56
  serial = Promise.resolve();
30
57
  reason;
31
58
  tripMarkerMissing = false;
32
- constructor(armed, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now) {
59
+ constructor(armed, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now, nextEpoch) {
33
60
  this.armed = armed;
34
61
  this.directory = directory;
35
62
  this.epoch = epoch;
@@ -39,6 +66,7 @@ export class WakeFuse {
39
66
  this.admissions = admissions;
40
67
  this.usageLedgerPath = usageLedgerPath;
41
68
  this.now = now;
69
+ this.nextEpoch = nextEpoch;
42
70
  }
43
71
  static async open(options) {
44
72
  const environment = options.environment ?? process.env;
@@ -47,20 +75,23 @@ export class WakeFuse {
47
75
  throw new Error("DAIMON_WAKE_FUSE must be exactly 'off' when set");
48
76
  if (environment.DAIMON_WAKE_FUSE_EPOCH !== undefined && nonBlank(environment.DAIMON_WAKE_FUSE_EPOCH) === undefined)
49
77
  throw new Error("DAIMON_WAKE_FUSE_EPOCH must be non-blank");
50
- const epoch = nonBlank(environment.DAIMON_WAKE_FUSE_EPOCH)
51
- // Agent ids are the stable organization identity available to Daimon.
52
- ?? `organization-${createHash("sha256").update(options.organizationKey).digest("hex")}`;
78
+ const pinned = nonBlank(environment.DAIMON_WAKE_FUSE_EPOCH);
79
+ const zone = nonBlank(environment[WAKE_FUSE_EPOCH_ZONE_ENV]) ?? "UTC";
80
+ // A pinned epoch never rolls; a derived one rolls with the operator's day.
81
+ // Agent ids are the stable organization identity available to Daimon.
82
+ const nextEpoch = (at) => pinned ?? derivedEpoch(options.organizationKey, at, zone);
53
83
  const maxWakes = ceiling(environment.DAIMON_WAKE_FUSE_MAX_WAKES, DEFAULT_WAKE_FUSE_MAX_WAKES, "DAIMON_WAKE_FUSE_MAX_WAKES");
54
84
  const maxTokens = ceiling(environment.DAIMON_WAKE_FUSE_MAX_TOKENS, DEFAULT_WAKE_FUSE_MAX_TOKENS, "DAIMON_WAKE_FUSE_MAX_TOKENS");
55
85
  const directory = resolveWakeFuseDirectory(environment);
56
86
  const now = options.now ?? (() => new Date());
87
+ const epoch = nextEpoch(now());
57
88
  const usageLedgerPath = resolveTurnUsageLedgerPath(environment);
58
89
  if (setting === "off") {
59
90
  if (!warnedDisarmed) {
60
91
  warnedDisarmed = true;
61
92
  console.error("DAIMON WAKE FUSE IS OFF: wake admission is unbounded");
62
93
  }
63
- return new WakeFuse(false, directory, epoch, now().toISOString(), maxWakes, maxTokens, new Set(), usageLedgerPath, now);
94
+ return new WakeFuse(false, directory, epoch, now().toISOString(), maxWakes, maxTokens, new Set(), usageLedgerPath, now, nextEpoch);
64
95
  }
65
96
  // Unbounded defaults would reproduce exactly the failure this module prevents.
66
97
  await readdir(directory);
@@ -73,7 +104,7 @@ export class WakeFuse {
73
104
  const admissions = new Set(records
74
105
  .filter((record) => record.kind === "admission" && record.epoch === epoch)
75
106
  .map((record) => key(record.agent, record.delivery)));
76
- const fuse = new WakeFuse(true, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now);
107
+ const fuse = new WakeFuse(true, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now, nextEpoch);
77
108
  if (await exists(path.join(directory, "fuse.stop")))
78
109
  fuse.reason = "operator_stop";
79
110
  else
@@ -89,7 +120,27 @@ export class WakeFuse {
89
120
  this.serial = result.then(() => undefined, () => undefined);
90
121
  return result;
91
122
  }
123
+ /**
124
+ * The budget as the caller should act on it NOW — which means after the day
125
+ * has been allowed to roll.
126
+ *
127
+ * This is the gate the scheduler actually consults: `AttentionDispatcher.drain()`
128
+ * returns at `snapshot().state !== "available"` before it ever reaches
129
+ * `admit()`. Rolling only inside `admitNow` therefore never fired for an
130
+ * organization that had hit its ceiling — it stayed paused across midnight
131
+ * and only a process restart could renew the budget. The roll is serialized
132
+ * with admissions so a snapshot and an admission never see two windows.
133
+ */
92
134
  async snapshot(agentId, limits = {}) {
135
+ const result = this.serial.catch(() => undefined).then(async () => {
136
+ if (this.armed)
137
+ await this.rollEpochIfDue();
138
+ return await this.snapshotNow(agentId, limits);
139
+ });
140
+ this.serial = result.then(() => undefined, () => undefined);
141
+ return await result;
142
+ }
143
+ async snapshotNow(agentId, limits = {}) {
93
144
  const used = this.admissions.size;
94
145
  const agentUsed = [...this.admissions].filter((entry) => entry.startsWith(`${bounded(agentId)}\u0000`)).length;
95
146
  const tokens = this.armed ? await sumTokens(this.usageLedgerPath, this.epochStartedAt) : 0;
@@ -111,7 +162,55 @@ export class WakeFuse {
111
162
  }
112
163
  tripped() { return this.reason; }
113
164
  async close() { await this.serial; }
165
+ /**
166
+ * Moves the counting window when the derived day has changed.
167
+ *
168
+ * Called at admission, which is the only moment the answer can matter: the
169
+ * organization runtime is a long-lived process, so an epoch computed once at
170
+ * open() would be the same epoch a week later.
171
+ *
172
+ * WHAT ROLLS AND WHAT DOES NOT
173
+ * ----------------------------
174
+ * A new window clears a CEILING trip, exactly as selecting a new epoch by
175
+ * hand always did: `readTripMarker` only honours a marker belonging to the
176
+ * current epoch. It does NOT clear an operator stop. `fuse.stop` is checked
177
+ * by existence and is deliberately not epoch-scoped, so a parked newsroom
178
+ * stays parked across midnight. That asymmetry is the whole point: a budget
179
+ * is a thing that renews, an operator stop is a decision that does not.
180
+ *
181
+ * Admissions are rebuilt from the ledger rather than emptied, so a clock that
182
+ * steps BACKWARD into a window that already spent wakes does not hand the
183
+ * organization a fresh budget.
184
+ */
185
+ async rollEpochIfDue() {
186
+ const wanted = this.nextEpoch(this.now());
187
+ if (wanted === this.epoch)
188
+ return;
189
+ const records = await readFuseRecords(this.directory);
190
+ const existingStart = records.filter((record) => record.kind === "epoch_start" && record.epoch === wanted).at(-1);
191
+ const startedAt = existingStart?.at ?? this.now().toISOString();
192
+ if (existingStart === undefined)
193
+ await append(this.directory, { v: WAKE_FUSE_VERSION, kind: "epoch_start", epoch: wanted, at: startedAt });
194
+ this.epoch = wanted;
195
+ this.epochStartedAt = startedAt;
196
+ this.admissions = new Set(records
197
+ .filter((record) => record.kind === "admission" && record.epoch === wanted)
198
+ .map((record) => key(record.agent, record.delivery)));
199
+ // Re-evaluate the trip for the NEW window. Clearing a CEILING is the part
200
+ // that does work here, and it is what makes the budget renew.
201
+ //
202
+ // The operator-stop branch is deliberately BELT AND BRACES, not the guard:
203
+ // `admitNow` checks `fuse.stop` itself on every admission, unscoped by
204
+ // epoch, and `trip()` sets the reason — so a park is already enforced
205
+ // whatever this line does. I could not write a failing test for it, and
206
+ // said so rather than dressing it up as a guarantee. It stays because it
207
+ // keeps the object's own state coherent at the moment it rolls; the test
208
+ // above pins the BEHAVIOUR, which is what must not regress.
209
+ this.reason = await exists(path.join(this.directory, "fuse.stop")) ? "operator_stop" : await readTripMarker(this.directory, wanted);
210
+ }
114
211
  async admitNow(agentId, deliveryId, limits) {
212
+ // Before the trip is consulted: a new day may have cleared a ceiling.
213
+ await this.rollEpochIfDue();
115
214
  if (this.reason !== undefined)
116
215
  return await this.trip(this.reason);
117
216
  try {
@@ -120,7 +219,7 @@ export class WakeFuse {
120
219
  const admissionKey = key(bounded(agentId), bounded(deliveryId));
121
220
  if (this.admissions.has(admissionKey))
122
221
  return { state: "admitted" };
123
- const budget = await this.snapshot(agentId, limits);
222
+ const budget = await this.snapshotNow(agentId, limits);
124
223
  if (budget.agent_executions_remaining === 0)
125
224
  return { state: "paused", reason: "agent_execution_ceiling" };
126
225
  if (budget.agent_tokens_remaining === 0)
@@ -0,0 +1,27 @@
1
+ import { type FileHandle } from "node:fs/promises";
2
+ import { WakeMomentIo } from "./wakeMomentIo.js";
3
+ export { WakeMomentFault } from "./wakeMomentIo.js";
4
+ /** Diagnostics never include source paths, file contents, or arbitrary exception text. */
5
+ export declare function wakeMomentFailure(error: unknown): string;
6
+ export type MomentStore = {
7
+ verify(io: WakeMomentIo, target?: string): Promise<void>;
8
+ close(): Promise<void>;
9
+ };
10
+ /** Pin the caller's root, then re-check the same dev/ino/uid/mode at every write boundary. */
11
+ export declare function pinMomentStore(directory: string, io: WakeMomentIo, verifyPrepared?: () => Promise<void>): Promise<MomentStore>;
12
+ export declare function openMomentDirectory(directory: string, io?: WakeMomentIo): Promise<FileHandle>;
13
+ export declare function ensureMomentDirectory(directory: string, io?: WakeMomentIo): Promise<void>;
14
+ export declare function syncMomentDirectory(directory: string, io?: WakeMomentIo): Promise<void>;
15
+ /** Refuses symlinks, special files and hard-linked metadata, including a FIFO before it can block. */
16
+ export declare function openMomentFile(file: string, flags?: number, io?: WakeMomentIo): Promise<FileHandle>;
17
+ export declare function manifestBytes(value: unknown, max?: number): string;
18
+ export declare function readMomentJson(file: string, io?: WakeMomentIo, max?: number): Promise<unknown>;
19
+ /** Unlike FileHandle.writeFile, each syscall is admitted through the cancellation fence. */
20
+ export declare function writeMomentBytes(fd: FileHandle, bytes: string | Buffer): Promise<void>;
21
+ export declare function writeMomentJson(file: string, bytes: string, io: WakeMomentIo, cleanup: Set<string>, verifyReplacement?: () => Promise<void>): Promise<void>;
22
+ export declare function momentPathExists(target: string, io?: WakeMomentIo): Promise<boolean>;
23
+ /** Never recurse until root identity AND the target's parent components have just been verified. */
24
+ export declare function cleanMomentPaths(paths: Set<string>, store: MomentStore, io: WakeMomentIo): Promise<void>;
25
+ /** Explicit traversal keeps recursive cleanup cancellable between individual filesystem calls. */
26
+ export declare function removeMomentPath(target: string, store: MomentStore, io: WakeMomentIo): Promise<void>;
27
+ //# sourceMappingURL=wakeMomentFs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wakeMomentFs.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeMomentFs.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKzD,OAAO,EAAmB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIxD;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAE/G,8FAA8F;AAC9F,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BpI;AAMD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAKzG;AAED,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrG;AAED,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnG;AAED,sGAAsG;AACtG,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAqB,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAO3H;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAoC,MAAM,GAAG,MAAM,CAInG;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,eAAqB,EAAE,GAAG,GAAoC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiB3I;AAED,4FAA4F;AAC5F,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5F;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjK;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhG;AAED,oGAAoG;AACpG,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9G;AAED,kGAAkG;AAClG,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAc1G"}