@noopolis/daimon 0.2.0 → 0.2.1

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 (255) 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/agySubscriptionBootstrap.js +1 -1
  67. package/dist/runtime/agySubscriptionRealm.js +1 -1
  68. package/dist/runtime/attention.d.ts +5 -0
  69. package/dist/runtime/attention.d.ts.map +1 -1
  70. package/dist/runtime/attention.js +14 -1
  71. package/dist/runtime/attentionDispatcher.d.ts +14 -2
  72. package/dist/runtime/attentionDispatcher.d.ts.map +1 -1
  73. package/dist/runtime/attentionDispatcher.js +127 -33
  74. package/dist/runtime/cli.js +5 -0
  75. package/dist/runtime/contract-manifest.json +1 -1
  76. package/dist/runtime/contract-manifest.sha256 +1 -1
  77. package/dist/runtime/engineBrokerCapabilities.d.ts +10 -0
  78. package/dist/runtime/engineBrokerCapabilities.d.ts.map +1 -1
  79. package/dist/runtime/engineBrokerCapabilities.js +10 -0
  80. package/dist/runtime/engineBrokerControlClient.d.ts +12 -1
  81. package/dist/runtime/engineBrokerControlClient.d.ts.map +1 -1
  82. package/dist/runtime/engineBrokerControlClient.js +139 -43
  83. package/dist/runtime/engineBrokerCredentialJournal.d.ts +2 -0
  84. package/dist/runtime/engineBrokerCredentialJournal.d.ts.map +1 -1
  85. package/dist/runtime/engineBrokerCredentialJournal.js +9 -0
  86. package/dist/runtime/engineBrokerInferenceProtocol.d.ts +3 -2
  87. package/dist/runtime/engineBrokerInferenceProtocol.d.ts.map +1 -1
  88. package/dist/runtime/engineBrokerInferenceProtocol.js +4 -4
  89. package/dist/runtime/engineBrokerMcpCallLog.d.ts +11 -0
  90. package/dist/runtime/engineBrokerMcpCallLog.d.ts.map +1 -1
  91. package/dist/runtime/engineBrokerMcpCallLog.js +13 -1
  92. package/dist/runtime/engineBrokerMcpFacade.d.ts +5 -2
  93. package/dist/runtime/engineBrokerMcpFacade.d.ts.map +1 -1
  94. package/dist/runtime/engineBrokerMcpFacade.js +5 -2
  95. package/dist/runtime/engineBrokerNativeClient.js +2 -2
  96. package/dist/runtime/engineBrokerProtocol.d.ts +42 -10
  97. package/dist/runtime/engineBrokerProtocol.d.ts.map +1 -1
  98. package/dist/runtime/engineBrokerProtocol.js +97 -14
  99. package/dist/runtime/engineBrokerSealLedger.d.ts.map +1 -1
  100. package/dist/runtime/engineBrokerSealLedger.js +9 -0
  101. package/dist/runtime/engineBrokerService.d.ts +8 -2
  102. package/dist/runtime/engineBrokerService.d.ts.map +1 -1
  103. package/dist/runtime/engineBrokerService.js +95 -37
  104. package/dist/runtime/engineBrokerServiceCli.js +1 -1
  105. package/dist/runtime/engineBrokerServiceConfig.d.ts +5 -0
  106. package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
  107. package/dist/runtime/engineBrokerServiceConfig.js +47 -9
  108. package/dist/runtime/engineBrokerTurnRegistry.d.ts +1 -1
  109. package/dist/runtime/engineBrokerTurnRegistry.d.ts.map +1 -1
  110. package/dist/runtime/engineBrokerTurnRegistry.js +7 -6
  111. package/dist/runtime/engineDispatcher.d.ts +2 -0
  112. package/dist/runtime/engineDispatcher.d.ts.map +1 -1
  113. package/dist/runtime/engineDispatcher.js +14 -2
  114. package/dist/runtime/engineReadiness.js +1 -1
  115. package/dist/runtime/grokBrokerCredentialAuthority.d.ts +1 -0
  116. package/dist/runtime/grokBrokerCredentialAuthority.d.ts.map +1 -1
  117. package/dist/runtime/grokBrokerCredentialAuthority.js +48 -22
  118. package/dist/runtime/grokBrokerCredentialReader.d.ts +1 -0
  119. package/dist/runtime/grokBrokerCredentialReader.d.ts.map +1 -1
  120. package/dist/runtime/grokBrokerCredentialReader.js +2 -1
  121. package/dist/runtime/grokBrokerProjection.d.ts +9 -1
  122. package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
  123. package/dist/runtime/grokBrokerProjection.js +11 -4
  124. package/dist/runtime/grokBrokerProviderClock.d.ts +26 -0
  125. package/dist/runtime/grokBrokerProviderClock.d.ts.map +1 -0
  126. package/dist/runtime/grokBrokerProviderClock.js +133 -0
  127. package/dist/runtime/grokBrokerProxy.d.ts +17 -3
  128. package/dist/runtime/grokBrokerProxy.d.ts.map +1 -1
  129. package/dist/runtime/grokBrokerProxy.js +73 -9
  130. package/dist/runtime/grokBrokerProxyJson.d.ts +10 -0
  131. package/dist/runtime/grokBrokerProxyJson.d.ts.map +1 -0
  132. package/dist/runtime/grokBrokerProxyJson.js +31 -0
  133. package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -1
  134. package/dist/runtime/grokBrokerProxyRequest.js +17 -6
  135. package/dist/runtime/grokBrokerRealmLease.js +1 -1
  136. package/dist/runtime/grokBrokerRefresh.js +1 -1
  137. package/dist/runtime/grokBrokerRunBudget.d.ts +20 -0
  138. package/dist/runtime/grokBrokerRunBudget.d.ts.map +1 -0
  139. package/dist/runtime/grokBrokerRunBudget.js +85 -0
  140. package/dist/runtime/grokBrokerSlot.d.ts +6 -0
  141. package/dist/runtime/grokBrokerSlot.d.ts.map +1 -0
  142. package/dist/runtime/grokBrokerSlot.js +21 -0
  143. package/dist/runtime/grokBrokerTaskClock.d.ts +32 -0
  144. package/dist/runtime/grokBrokerTaskClock.d.ts.map +1 -0
  145. package/dist/runtime/grokBrokerTaskClock.js +120 -0
  146. package/dist/runtime/grokBrokerTurnMeter.d.ts +55 -3
  147. package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -1
  148. package/dist/runtime/grokBrokerTurnMeter.js +90 -4
  149. package/dist/runtime/grokBrokerWorkerConfig.d.ts +5 -2
  150. package/dist/runtime/grokBrokerWorkerConfig.d.ts.map +1 -1
  151. package/dist/runtime/grokBrokerWorkerConfig.js +22 -3
  152. package/dist/runtime/grokEngineBroker.d.ts +43 -3
  153. package/dist/runtime/grokEngineBroker.d.ts.map +1 -1
  154. package/dist/runtime/grokEngineBroker.js +159 -71
  155. package/dist/runtime/grokEngineBrokerTurn.d.ts +32 -8
  156. package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -1
  157. package/dist/runtime/grokEngineBrokerTurn.js +204 -21
  158. package/dist/runtime/grokEngineBrokerTurnEnd.d.ts +88 -0
  159. package/dist/runtime/grokEngineBrokerTurnEnd.d.ts.map +1 -0
  160. package/dist/runtime/grokEngineBrokerTurnEnd.js +148 -0
  161. package/dist/runtime/grokInferenceProxy.d.ts.map +1 -1
  162. package/dist/runtime/grokInferenceProxy.js +3 -0
  163. package/dist/runtime/grokSubscriptionRealm.js +1 -1
  164. package/dist/runtime/grokWorkerAttestation.d.ts +9 -4
  165. package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
  166. package/dist/runtime/grokWorkerAttestation.js +2 -2
  167. package/dist/runtime/grokWorkerHomeAttestation.d.ts +4 -2
  168. package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
  169. package/dist/runtime/grokWorkerHomeAttestation.js +10 -4
  170. package/dist/runtime/index.d.ts +7 -2
  171. package/dist/runtime/index.d.ts.map +1 -1
  172. package/dist/runtime/index.js +5 -1
  173. package/dist/runtime/native/daimon-engine-broker +0 -0
  174. package/dist/runtime/organizationRuntime.d.ts +3 -0
  175. package/dist/runtime/organizationRuntime.d.ts.map +1 -1
  176. package/dist/runtime/organizationRuntimeAdmission.d.ts +6 -0
  177. package/dist/runtime/organizationRuntimeAdmission.d.ts.map +1 -0
  178. package/dist/runtime/organizationRuntimeAdmission.js +22 -0
  179. package/dist/runtime/organizationRuntimeControl.d.ts +9 -0
  180. package/dist/runtime/organizationRuntimeControl.d.ts.map +1 -1
  181. package/dist/runtime/organizationRuntimeControl.js +72 -24
  182. package/dist/runtime/organizationRuntimeHost.d.ts.map +1 -1
  183. package/dist/runtime/organizationRuntimeHost.js +13 -4
  184. package/dist/runtime/organizationRuntimeParsing.d.ts.map +1 -1
  185. package/dist/runtime/organizationRuntimeParsing.js +4 -1
  186. package/dist/runtime/organizationRuntimeReadiness.d.ts.map +1 -1
  187. package/dist/runtime/organizationRuntimeReadiness.js +8 -0
  188. package/dist/runtime/organizationRuntimeRecording.d.ts +7 -0
  189. package/dist/runtime/organizationRuntimeRecording.d.ts.map +1 -0
  190. package/dist/runtime/organizationRuntimeRecording.js +58 -0
  191. package/dist/runtime/physicalReadiness.d.ts +12 -0
  192. package/dist/runtime/physicalReadiness.d.ts.map +1 -1
  193. package/dist/runtime/physicalReadiness.js +43 -9
  194. package/dist/runtime/productionAgentTools.d.ts.map +1 -1
  195. package/dist/runtime/productionAgentTools.js +13 -5
  196. package/dist/runtime/relocateRuntimeConfig.d.ts +14 -0
  197. package/dist/runtime/relocateRuntimeConfig.d.ts.map +1 -0
  198. package/dist/runtime/relocateRuntimeConfig.js +38 -0
  199. package/dist/runtime/schedule.d.ts.map +1 -1
  200. package/dist/runtime/schedule.js +12 -7
  201. package/dist/runtime/storeCoordination.d.ts +10 -0
  202. package/dist/runtime/storeCoordination.d.ts.map +1 -1
  203. package/dist/runtime/storeCoordination.js +23 -0
  204. package/dist/runtime/taskClock.d.ts +42 -0
  205. package/dist/runtime/taskClock.d.ts.map +1 -0
  206. package/dist/runtime/taskClock.js +102 -0
  207. package/dist/runtime/taskClockProcess.d.ts +17 -0
  208. package/dist/runtime/taskClockProcess.d.ts.map +1 -0
  209. package/dist/runtime/taskClockProcess.js +58 -0
  210. package/dist/runtime/turnRequestLedger.d.ts +0 -5
  211. package/dist/runtime/turnRequestLedger.d.ts.map +1 -1
  212. package/dist/runtime/turnRequestLedger.js +23 -21
  213. package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts +34 -0
  214. package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts.map +1 -0
  215. package/dist/runtime/wakeAcceptanceClaimDiagnostics.js +79 -0
  216. package/dist/runtime/wakeAcceptanceIndex.d.ts +55 -0
  217. package/dist/runtime/wakeAcceptanceIndex.d.ts.map +1 -0
  218. package/dist/runtime/wakeAcceptanceIndex.js +158 -0
  219. package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts +9 -1
  220. package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts.map +1 -1
  221. package/dist/runtime/wakeAcceptanceProcessIdentity.js +5 -1
  222. package/dist/runtime/wakeAcceptanceRecord.d.ts +2 -0
  223. package/dist/runtime/wakeAcceptanceRecord.d.ts.map +1 -1
  224. package/dist/runtime/wakeAcceptanceRecord.js +4 -2
  225. package/dist/runtime/wakeAcceptanceRetention.d.ts +25 -1
  226. package/dist/runtime/wakeAcceptanceRetention.d.ts.map +1 -1
  227. package/dist/runtime/wakeAcceptanceRetention.js +28 -2
  228. package/dist/runtime/wakeAcceptanceStore.d.ts +8 -8
  229. package/dist/runtime/wakeAcceptanceStore.d.ts.map +1 -1
  230. package/dist/runtime/wakeAcceptanceStore.js +66 -63
  231. package/dist/runtime/wakeFuse.d.ts +58 -3
  232. package/dist/runtime/wakeFuse.d.ts.map +1 -1
  233. package/dist/runtime/wakeFuse.js +106 -7
  234. package/dist/runtime/wakeMomentFs.d.ts +27 -0
  235. package/dist/runtime/wakeMomentFs.d.ts.map +1 -0
  236. package/dist/runtime/wakeMomentFs.js +223 -0
  237. package/dist/runtime/wakeMomentHistory.d.ts +26 -0
  238. package/dist/runtime/wakeMomentHistory.d.ts.map +1 -0
  239. package/dist/runtime/wakeMomentHistory.js +109 -0
  240. package/dist/runtime/wakeMomentIo.d.ts +22 -0
  241. package/dist/runtime/wakeMomentIo.d.ts.map +1 -0
  242. package/dist/runtime/wakeMomentIo.js +114 -0
  243. package/dist/runtime/wakeMomentRecorder.d.ts +33 -0
  244. package/dist/runtime/wakeMomentRecorder.d.ts.map +1 -0
  245. package/dist/runtime/wakeMomentRecorder.js +81 -0
  246. package/dist/runtime/wakeMomentRetention.d.ts +7 -0
  247. package/dist/runtime/wakeMomentRetention.d.ts.map +1 -0
  248. package/dist/runtime/wakeMomentRetention.js +143 -0
  249. package/dist/runtime/wakeMomentRows.d.ts +8 -0
  250. package/dist/runtime/wakeMomentRows.d.ts.map +1 -0
  251. package/dist/runtime/wakeMomentRows.js +66 -0
  252. package/dist/runtime/wakeMomentSnapshot.d.ts +56 -0
  253. package/dist/runtime/wakeMomentSnapshot.d.ts.map +1 -0
  254. package/dist/runtime/wakeMomentSnapshot.js +248 -0
  255. package/package.json +4 -4
@@ -0,0 +1,102 @@
1
+ export const TASK_CLOCK_ENV = "NOOPOLIS_TASK_CLOCK";
2
+ export const TASK_CLOCK_VERSION = "noopolis.task-clock.v1";
3
+ export const TASK_CLOCK_MIN_MS = 0;
4
+ export const TASK_CLOCK_MAX_MS = 253_402_300_799_999;
5
+ export function validTaskInstant(value) {
6
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= TASK_CLOCK_MIN_MS && value <= TASK_CLOCK_MAX_MS;
7
+ }
8
+ export function assertTaskInstant(value) {
9
+ if (!validTaskInstant(value))
10
+ throw invalid("task instant must be within 1970-01-01T00:00:00.000Z .. 9999-12-31T23:59:59.999Z");
11
+ return value;
12
+ }
13
+ /** Environment-only contract: no clock callbacks or compiler config in the public API. */
14
+ export function readTaskClock(environment = process.env) {
15
+ return parseTaskClock(environment[TASK_CLOCK_ENV]);
16
+ }
17
+ export function parseTaskClock(raw) {
18
+ if (raw === undefined)
19
+ return undefined;
20
+ let value;
21
+ try {
22
+ value = JSON.parse(raw);
23
+ }
24
+ catch {
25
+ throw invalid("must be valid JSON");
26
+ }
27
+ if (value === null || typeof value !== "object" || Array.isArray(value))
28
+ throw invalid("must be an object");
29
+ const fields = value;
30
+ if (Object.keys(fields).sort().join() !== "anchorEpochMs,origin,version")
31
+ throw invalid("requires exactly version, origin, anchorEpochMs (no unknown fields)");
32
+ if (fields.version !== TASK_CLOCK_VERSION)
33
+ throw invalid(`version must be ${TASK_CLOCK_VERSION}`);
34
+ if (typeof fields.origin !== "string" || !validInstant(fields.origin))
35
+ throw invalid("origin must be a valid ISO-8601 instant with Z or an explicit offset");
36
+ if (typeof fields.anchorEpochMs !== "number" || !Number.isSafeInteger(fields.anchorEpochMs))
37
+ throw invalid("anchorEpochMs must be a safe integer");
38
+ const origin = fields.origin, anchorEpochMs = fields.anchorEpochMs, originMs = Date.parse(origin);
39
+ assertTaskInstant(originMs);
40
+ const at = (realEpochMs) => assertTaskInstant(originMs + (realEpochMs - anchorEpochMs));
41
+ return Object.freeze({ raw, origin, anchorEpochMs, at, now: () => at(Date.now()) });
42
+ }
43
+ /** The caller installs libfaketime; Daimon derives one process-independent offset. */
44
+ export function taskClockChildEnvironment(declared = {}, clock = readTaskClock(), environment = process.env) {
45
+ if (clock === undefined)
46
+ return {};
47
+ for (const name of Object.keys(declared)) {
48
+ if (declared[name] !== undefined && clockVariable(name))
49
+ throw invalid(`declared child environment conflicts with ${name}`);
50
+ }
51
+ // Integer arithmetic keeps every millisecond even at the safe-integer anchor limits.
52
+ const offset = BigInt(Date.parse(clock.origin)) - BigInt(clock.anchorEpochMs);
53
+ const magnitude = offset < 0n ? -offset : offset;
54
+ const fraction = magnitude % 1000n;
55
+ const seconds = `${offset < 0n ? "-" : "+"}${magnitude / 1000n}${fraction === 0n ? "" : `.${String(fraction).padStart(3, "0")}`}`;
56
+ const preload = taskClockPreload(environment.LD_PRELOAD);
57
+ if (preload === undefined)
58
+ throw invalid("process clock startup probe requires caller-provided LD_PRELOAD naming libfaketime*.so*; refusing clocked execution");
59
+ return {
60
+ // The child process clock owns the offset; Mneme and nested consumers use Date.now().
61
+ // libfaketime's relative format defaults to seconds; an `s` suffix is not supported.
62
+ FAKETIME: seconds,
63
+ FAKETIME_DONT_FAKE_MONOTONIC: "1",
64
+ // libfaketime parses decimal fractions using the process locale.
65
+ LC_ALL: "C",
66
+ LD_PRELOAD: preload
67
+ };
68
+ }
69
+ /** Inherited environments must not reintroduce another offset owner or loader controls. */
70
+ export function taskClockProcessEnvironment(inherited, derived = taskClockChildEnvironment()) {
71
+ if (Object.keys(derived).length === 0)
72
+ return inherited;
73
+ return { ...Object.fromEntries(Object.entries(inherited).filter(([name]) => !clockVariable(name))), ...derived };
74
+ }
75
+ function clockVariable(name) {
76
+ return /^(?:LD_PRELOAD$|DYLD_|FAKETIME|NOOPOLIS_TASK_CLOCK$|MNEME_CLOCK_)/u.test(name);
77
+ }
78
+ /** Provider/auth/control processes must never inherit the agent's process clock. */
79
+ export function realTimeProcessEnvironment(inherited) {
80
+ if (readTaskClock() === undefined)
81
+ return inherited;
82
+ return Object.fromEntries(Object.entries(inherited).filter(([name]) => !clockVariable(name)));
83
+ }
84
+ /** Reject mixed loader lists: unrelated libraries must never ride this exception. */
85
+ export function taskClockPreload(value) {
86
+ const libraries = value?.split(/[\s:]+/u).filter(Boolean);
87
+ return libraries?.length && libraries.every((library) => /^libfaketime[^/]*\.so[^/]*$/u.test(library.split("/").at(-1))) ? value : undefined;
88
+ }
89
+ /** Project real delivery/receipt timestamps, never historical dates inside tool payloads. */
90
+ export function taskClockTimestamp(realTimestamp, clock = readTaskClock()) {
91
+ return clock === undefined ? realTimestamp : new Date(clock.at(Date.parse(realTimestamp))).toISOString();
92
+ }
93
+ function invalid(reason) { return new Error(`${TASK_CLOCK_ENV}: ${reason}`); }
94
+ function validInstant(value) {
95
+ const match = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):?[0-5]\d)$/u.exec(value);
96
+ if (match === null || !Number.isFinite(Date.parse(value)))
97
+ return false;
98
+ const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
99
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
100
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
101
+ return month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1];
102
+ }
@@ -0,0 +1,17 @@
1
+ import childProcess from "node:child_process";
2
+ import { type TaskClock } from "./taskClock.js";
3
+ export type { ChildProcess } from "node:child_process";
4
+ /** @internal Synchronous injection seam; never runtime configuration. */
5
+ export type TaskClockProcessProbe = (command: string, args: readonly string[], environment: NodeJS.ProcessEnv) => string;
6
+ /** @internal Isolate simulated probes between tests. */
7
+ export declare function resetTaskClockProcessForTest(): void;
8
+ /** Shared child-launch boundary, including SDK transports and Pi's spawn hook.
9
+ * Keep the synchronous spawn API: only the first launch blocks for the bounded probes.
10
+ * Every launch derives a fresh environment; only successful interposition is memoized.
11
+ */
12
+ export declare function readyTaskClockEnvironment(environment: NodeJS.ProcessEnv, clock?: TaskClock | undefined, probe?: TaskClockProcessProbe): NodeJS.ProcessEnv;
13
+ /** Only agent-facing tool/server children cross the shifted readiness gate. */
14
+ export declare const spawn: typeof childProcess.spawn;
15
+ /** Engine CLIs and operational helpers stay real; no libfaketime probe runs here. */
16
+ export declare const spawnRealTime: typeof childProcess.spawn;
17
+ //# sourceMappingURL=taskClockProcess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taskClockProcess.d.ts","sourceRoot":"","sources":["../../src/runtime/taskClockProcess.ts"],"names":[],"mappings":"AAAA,OAAO,YAAmC,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAqG,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEnJ,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,yEAAyE;AACzE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC;AAGzH,wDAAwD;AACxD,wBAAgB,4BAA4B,IAAI,IAAI,CAAsB;AAS1E;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,GAAE,SAAS,GAAG,SAA2B,EAAE,KAAK,GAAE,qBAAiC,GACvH,MAAM,CAAC,UAAU,CAyBnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,KAAK,EAAE,OAAO,YAAY,CAAC,KAIT,CAAC;AAEhC,qFAAqF;AACrF,eAAO,MAAM,aAAa,EAAE,OAAO,YAAY,CAAC,KAIjB,CAAC"}
@@ -0,0 +1,58 @@
1
+ import childProcess from "node:child_process";
2
+ import { readTaskClock, realTimeProcessEnvironment, taskClockChildEnvironment, taskClockProcessEnvironment } from "./taskClock.js";
3
+ const verified = new Set();
4
+ /** @internal Isolate simulated probes between tests. */
5
+ export function resetTaskClockProcessForTest() { verified.clear(); }
6
+ const dateProbe = (command, args, env) => {
7
+ const result = childProcess.spawnSync(command, [...args], { env, encoding: "utf8", timeout: 5000, killSignal: "SIGKILL", maxBuffer: 1024 });
8
+ // Loaders can ignore a missing library and still exit zero, including at zero offset.
9
+ if (result.error || result.status !== 0 || result.stderr?.trim())
10
+ throw result.error ?? new Error("date/libfaketime emitted a startup diagnostic");
11
+ return result.stdout;
12
+ };
13
+ /** Shared child-launch boundary, including SDK transports and Pi's spawn hook.
14
+ * Keep the synchronous spawn API: only the first launch blocks for the bounded probes.
15
+ * Every launch derives a fresh environment; only successful interposition is memoized.
16
+ */
17
+ export function readyTaskClockEnvironment(environment, clock = readTaskClock(), probe = dateProbe) {
18
+ if (clock === undefined)
19
+ return environment;
20
+ const refuse = (reason) => new Error(`NOOPOLIS_TASK_CLOCK: process clock startup probe ${reason}; refusing clocked execution`);
21
+ if (process.platform !== "linux")
22
+ throw refuse("requires Linux libfaketime interposition");
23
+ const derived = taskClockChildEnvironment({}, clock);
24
+ const env = taskClockProcessEnvironment(environment, derived);
25
+ clock.now(); // A cached process clock never permits reads outside the supported calendar.
26
+ const key = JSON.stringify([process.pid, process.platform, derived.LD_PRELOAD, derived.FAKETIME]);
27
+ if (verified.has(key))
28
+ return env;
29
+ for (const sentinel of [true, false]) {
30
+ let output;
31
+ try {
32
+ // Absolute path: engine-specific PATHs need not contain system utilities.
33
+ output = probe("/bin/date", ["-u", "+%s"], sentinel ? { ...env, FAKETIME: "-31536000" } : env);
34
+ }
35
+ catch (cause) {
36
+ throw new Error(refuse("could not run date -u +%s (date/libfaketime missing or unusable)").message, { cause });
37
+ }
38
+ const seconds = Number(output.trim());
39
+ const expected = sentinel ? Date.now() - 31_536_000_000 : clock.now();
40
+ if (!/^-?\d+$/u.test(output.trim()) || !Number.isSafeInteger(seconds) || Math.abs(seconds * 1000 - expected) > 5000) {
41
+ throw refuse(`did not observe ${sentinel ? "sentinel offset" : "task time"} within ±5 seconds (check libfaketime and its offset)`);
42
+ }
43
+ }
44
+ verified.add(key);
45
+ return env;
46
+ }
47
+ /** Only agent-facing tool/server children cross the shifted readiness gate. */
48
+ export const spawn = ((command, args, options) => {
49
+ const argv = Array.isArray(args) ? args : [];
50
+ const settings = (Array.isArray(args) ? options : args ?? options);
51
+ return childProcess.spawn(command, argv, { ...settings, env: readyTaskClockEnvironment(settings?.env ?? process.env) });
52
+ });
53
+ /** Engine CLIs and operational helpers stay real; no libfaketime probe runs here. */
54
+ export const spawnRealTime = ((command, args, options) => {
55
+ const argv = Array.isArray(args) ? args : [];
56
+ const settings = (Array.isArray(args) ? options : args ?? options);
57
+ return childProcess.spawn(command, argv, { ...settings, env: realTimeProcessEnvironment(settings?.env ?? process.env) });
58
+ });
@@ -123,11 +123,6 @@ export type GrokTurnRequestEntry = Readonly<{
123
123
  * field is not.
124
124
  */
125
125
  export declare const renderGrokTurnRequestLines: (entry: GrokTurnRequestEntry) => string;
126
- /**
127
- * Append already-rendered, newline-terminated ledger lines in one write, with the
128
- * same rotation and file mode as both ledgers. Advisory: never rejects. The
129
- * broker uses it to append the exact bytes it sealed into a turn record.
130
- */
131
126
  export declare const recordLedgerLines: (file: string, lines: string) => Promise<boolean>;
132
127
  /** Advisory and never rejects, like {@link recordTurnRequests}; an empty turn writes nothing. */
133
128
  export declare const recordGrokTurnRequests: (file: string, entry: GrokTurnRequestEntry) => Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"turnRequestLedger.d.ts","sourceRoot":"","sources":["../../src/runtime/turnRequestLedger.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,EAAG,kCAA2C,CAAC;AAEvF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,EAAG,kCAA2C,CAAC;AAExF,eAAO,MAAM,4BAA4B,GAAI,cAAa,MAAM,CAAC,UAAwB,KAAG,MAG3F,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAIH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,gBAAgB,KAAG,MAqBhE,CAAC;AASF,0FAA0F;AAC1F;;;;GAIG;AACH;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AACvQ;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/M;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,oBAAoB,KAAG,MAuBxE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAU,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,OAAO,CAGpF,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,GAAU,MAAM,MAAM,EAAE,OAAO,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAGvG,CAAC;AA4BF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAU,MAAM,MAAM,EAAE,OAAO,gBAAgB,KAAG,OAAO,CAAC,OAAO,CAU/F,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GAAU,OAAO,QAAQ,CAAC;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC,KAAG,OAAO,CAAC,OAAO,CAiBnB,CAAC"}
1
+ {"version":3,"file":"turnRequestLedger.d.ts","sourceRoot":"","sources":["../../src/runtime/turnRequestLedger.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4B,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,EAAG,kCAA2C,CAAC;AAEvF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,EAAG,kCAA2C,CAAC;AAExF,eAAO,MAAM,4BAA4B,GAAI,cAAa,MAAM,CAAC,UAAwB,KAAG,MAG3F,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAIH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,gBAAgB,KAAG,MAqBhE,CAAC;AASF,0FAA0F;AAC1F;;;;GAIG;AACH;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AACvQ;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/M;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,oBAAoB,KAAG,MAuBxE,CAAC;AASF,eAAO,MAAM,iBAAiB,GAAU,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,OAAO,CAWpF,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,GAAU,MAAM,MAAM,EAAE,OAAO,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAGvG,CAAC;AAoBF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAU,MAAM,MAAM,EAAE,OAAO,gBAAgB,KAAG,OAAO,CAAC,OAAO,CAQ/F,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GAAU,OAAO,QAAQ,CAAC;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC,KAAG,OAAO,CAAC,OAAO,CAiBnB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { constants } from "node:fs";
2
3
  import { open, rename, stat } from "node:fs/promises";
3
4
  import { readCodexRolloutRequests } from "../pi/codexRolloutUsage.js";
@@ -138,16 +139,29 @@ export const renderGrokTurnRequestLines = (entry) => {
138
139
  * same rotation and file mode as both ledgers. Advisory: never rejects. The
139
140
  * broker uses it to append the exact bytes it sealed into a turn record.
140
141
  */
142
+ const ledgerWrites = new Map();
141
143
  export const recordLedgerLines = async (file, lines) => {
142
144
  if (lines.length === 0)
143
145
  return false;
146
+ const key = path.resolve(file);
147
+ const previous = ledgerWrites.get(key) ?? Promise.resolve(true);
148
+ const pending = previous.then(async () => {
149
+ try {
150
+ await rotate(key);
151
+ await appendLines(key, lines);
152
+ return true;
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ });
158
+ ledgerWrites.set(key, pending);
144
159
  try {
145
- await rotate(file);
146
- await appendLines(file, lines);
147
- return true;
160
+ return await pending;
148
161
  }
149
- catch {
150
- return false;
162
+ finally {
163
+ if (ledgerWrites.get(key) === pending)
164
+ ledgerWrites.delete(key);
151
165
  }
152
166
  };
153
167
  /** Advisory and never rejects, like {@link recordTurnRequests}; an empty turn writes nothing. */
@@ -155,9 +169,7 @@ export const recordGrokTurnRequests = async (file, entry) => {
155
169
  if (entry.requests.length === 0)
156
170
  return false;
157
171
  try {
158
- await rotate(file);
159
- await appendLines(file, renderGrokTurnRequestLines(entry));
160
- return true;
172
+ return await recordLedgerLines(file, renderGrokTurnRequestLines(entry));
161
173
  }
162
174
  catch {
163
175
  return false;
@@ -174,16 +186,8 @@ const rotate = async (file) => {
174
186
  if (size >= TURN_USAGE_ROTATE_BYTES)
175
187
  await rename(file, `${file}.1`);
176
188
  };
177
- /**
178
- * One `write(2)` for the whole wake's rows.
179
- *
180
- * Turns for different agents run concurrently inside the single broker process.
181
- * `O_APPEND` makes a single sub-page write atomic against them, so a wake's rows
182
- * are contiguous and never interleaved with another wake's — which matters more
183
- * here than in the per-wake ledger, because a row only means something beside
184
- * the other rows of its own thread. A wake with enough requests to exceed a page
185
- * is still append-ordered per line; interleaving would cost grouping, not
186
- * correctness, since every row carries its own thread id.
189
+ /** Rotation and all writes are serialized per canonical path in this broker process.
190
+ * The realm lease excludes another broker. Independent paths remain concurrent.
187
191
  */
188
192
  const appendLines = async (file, lines) => {
189
193
  const bytes = Buffer.from(lines, "utf8");
@@ -209,9 +213,7 @@ export const recordTurnRequests = async (file, entry) => {
209
213
  if (entry.requests.length === 0)
210
214
  return false;
211
215
  try {
212
- await rotate(file);
213
- await appendLines(file, renderTurnRequestLines(entry));
214
- return true;
216
+ return await recordLedgerLines(file, renderTurnRequestLines(entry));
215
217
  }
216
218
  catch {
217
219
  /* advisory: instrumentation never fails a wake that published */
@@ -0,0 +1,34 @@
1
+ import type { WakeExecutionClaim } from "./wakeAcceptanceStore.js";
2
+ import { type TransitionLock } from "./wakeAcceptanceProcessIdentity.js";
3
+ type Identity = WakeExecutionClaim | TransitionLock | string | null;
4
+ export type WakeClaimFailure = Readonly<{
5
+ step: string;
6
+ assertion: string;
7
+ acceptance_id: string;
8
+ expected: Identity;
9
+ current: Identity;
10
+ lock?: TransitionLock | null;
11
+ }>;
12
+ export declare class WakeExecutionClaimLostError extends Error {
13
+ readonly detail?: WakeClaimFailure | undefined;
14
+ constructor(detail?: WakeClaimFailure | undefined, cause?: unknown);
15
+ }
16
+ export declare class WakeTransitionLockContentionError extends WakeExecutionClaimLostError {
17
+ constructor(detail: WakeClaimFailure);
18
+ }
19
+ type LockWaitTiming = Readonly<{
20
+ now: () => number;
21
+ sleep: (ms: number) => Promise<void>;
22
+ }>;
23
+ /**
24
+ * Only an occupied lock is retried; claim mismatches and I/O failures are not.
25
+ * The 250 ms budget bounds attempt starts and requested sleeps. Filesystem I/O
26
+ * is not cancellable, so an admitted acquisition can finish after the deadline.
27
+ * Keep that lock: callers revalidate the claim before writing and release the
28
+ * lock in finally, so late acquisition cannot authorize a stale claim.
29
+ */
30
+ export declare function waitForClaimTransitionLock(acquire: () => Promise<TransitionLock | undefined>, read: () => Promise<TransitionLock | undefined>, acceptanceId: string, claim: WakeExecutionClaim, step: string, timing?: LockWaitTiming): Promise<TransitionLock>;
31
+ /** Read errors retain their errno as cause; no record/event/credential data enters diagnostics. */
32
+ export declare function readExpectedClaim(read: () => Promise<WakeExecutionClaim>, acceptanceId: string, expected: WakeExecutionClaim, step: string, now: () => number, lock?: TransitionLock): Promise<WakeExecutionClaim>;
33
+ export {};
34
+ //# sourceMappingURL=wakeAcceptanceClaimDiagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wakeAcceptanceClaimDiagnostics.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeAcceptanceClaimDiagnostics.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzG,KAAK,QAAQ,GAAG,kBAAkB,GAAG,cAAc,GAAG,MAAM,GAAG,IAAI,CAAC;AACpE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AACzK,qBAAa,2BAA4B,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB;gBAAzB,MAAM,CAAC,EAAE,gBAAgB,YAAA,EAAE,KAAK,CAAC,EAAE,OAAO;CAIhE;AACD,qBAAa,iCAAkC,SAAQ,2BAA2B;gBACpE,MAAM,EAAE,gBAAgB;CACrC;AAED,KAAK,cAAc,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAAC;AAE5F;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,cAA+B,GAAG,OAAO,CAAC,cAAc,CAAC,CAiBrR;AAUD,mGAAmG;AACnG,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAcxN"}
@@ -0,0 +1,79 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { performance } from "node:perf_hooks";
3
+ import { WakeTransitionLockBlockedError } from "./wakeAcceptanceProcessIdentity.js";
4
+ export class WakeExecutionClaimLostError extends Error {
5
+ detail;
6
+ constructor(detail, cause) {
7
+ super(`wake execution claim was lost${detail === undefined ? "" : `: step=${detail.step} assertion=${detail.assertion} ${JSON.stringify({ ...detail, expected: diagnosticIdentity(detail.expected), current: diagnosticIdentity(detail.current) })}`}`, { cause });
8
+ this.detail = detail;
9
+ this.name = "WakeExecutionClaimLostError";
10
+ }
11
+ }
12
+ export class WakeTransitionLockContentionError extends WakeExecutionClaimLostError {
13
+ constructor(detail) { super(detail); this.name = "WakeTransitionLockContentionError"; }
14
+ }
15
+ const lockWaitTiming = { now: () => performance.now(), sleep: async (ms) => { await delay(ms); } };
16
+ /**
17
+ * Only an occupied lock is retried; claim mismatches and I/O failures are not.
18
+ * The 250 ms budget bounds attempt starts and requested sleeps. Filesystem I/O
19
+ * is not cancellable, so an admitted acquisition can finish after the deadline.
20
+ * Keep that lock: callers revalidate the claim before writing and release the
21
+ * lock in finally, so late acquisition cannot authorize a stale claim.
22
+ */
23
+ export async function waitForClaimTransitionLock(acquire, read, acceptanceId, claim, step, timing = lockWaitTiming) {
24
+ const deadline = timing.now() + 250;
25
+ for (let attempt = 0;; attempt++) {
26
+ if (attempt >= 26 || timing.now() >= deadline) {
27
+ let current;
28
+ try {
29
+ current = await read();
30
+ }
31
+ catch (cause) {
32
+ throw new WakeExecutionClaimLostError({ step, assertion: "transition_lock_read", acceptance_id: acceptanceId, expected: claim, current: null }, cause);
33
+ }
34
+ throw new WakeTransitionLockContentionError({ step, assertion: "transition_lock_contended", acceptance_id: acceptanceId, expected: claim, current: current ?? null, lock: current ?? null });
35
+ }
36
+ let lock;
37
+ try {
38
+ lock = await acquire();
39
+ }
40
+ catch (cause) {
41
+ if (cause instanceof WakeTransitionLockBlockedError)
42
+ throw cause;
43
+ throw new WakeExecutionClaimLostError({ step, assertion: "transition_lock_acquire", acceptance_id: acceptanceId, expected: claim, current: null }, cause);
44
+ }
45
+ if (lock !== undefined)
46
+ return lock;
47
+ const remaining = deadline - timing.now();
48
+ if (remaining > 0 && attempt < 25)
49
+ await timing.sleep(Math.min(10, remaining));
50
+ }
51
+ }
52
+ function diagnosticIdentity(value) {
53
+ if (value === null || typeof value === "string" || "pid" in value)
54
+ return value;
55
+ // Membership can contain 32 UUIDs. Keep both identities and the lock visible
56
+ // through the host's 2 KiB diagnostic bound; the typed detail keeps membership.
57
+ const { acceptance_ids: _members, ...identity } = value;
58
+ return identity;
59
+ }
60
+ /** Read errors retain their errno as cause; no record/event/credential data enters diagnostics. */
61
+ export async function readExpectedClaim(read, acceptanceId, expected, step, now, lock) {
62
+ let current;
63
+ try {
64
+ current = await read();
65
+ }
66
+ catch (cause) {
67
+ const assertion = cause?.code === "ENOENT" ? "claim_missing" : "claim_read";
68
+ throw new WakeExecutionClaimLostError({ step, assertion, acceptance_id: acceptanceId, expected, current: null, ...(lock === undefined ? {} : { lock }) }, cause);
69
+ }
70
+ const assertion = !(current.acceptance_id === acceptanceId || current.acceptance_ids?.includes(acceptanceId)) ? "claim_membership"
71
+ : current.acceptance_id !== expected.acceptance_id ? "claim_acceptance_id"
72
+ : current.owner_id !== expected.owner_id ? "claim_owner_id"
73
+ : current.generation !== expected.generation ? "claim_generation"
74
+ : current.expires_at !== expected.expires_at ? "claim_expires_at"
75
+ : Date.parse(current.expires_at) <= now() ? "claim_expired" : undefined;
76
+ if (assertion !== undefined)
77
+ throw new WakeExecutionClaimLostError({ step, assertion, acceptance_id: acceptanceId, expected, current, ...(lock === undefined ? {} : { lock }) });
78
+ return current;
79
+ }
@@ -0,0 +1,55 @@
1
+ export type WakeAcceptanceStat = Readonly<{
2
+ ino: bigint;
3
+ size: bigint;
4
+ mtimeNs: bigint;
5
+ ctimeNs: bigint;
6
+ }>;
7
+ export type WakeAcceptanceIndexSource = Readonly<{
8
+ /** Opened-handle stat of the store directory. */
9
+ directoryStat: () => Promise<WakeAcceptanceStat>;
10
+ /** Record file names, after the store's own root validation and bounds. */
11
+ files: () => Promise<readonly string[]>;
12
+ /** Acceptance id of one record, read through every safety check of the store. */
13
+ readId: (file: string) => Promise<string>;
14
+ /** Identity stamp of one record file; undefined when it is gone. */
15
+ fileStat: (file: string) => Promise<WakeAcceptanceStat | undefined>;
16
+ }>;
17
+ /**
18
+ * Acceptance-id → record-file index. Own writes keep it current; a directory
19
+ * whose stamp moved since the last sync (another process, or this one) is
20
+ * re-listed, and only record files whose own stamp moved are read again. A miss
21
+ * against an unchanged directory reads no record file.
22
+ */
23
+ export declare class WakeAcceptanceIndex {
24
+ private readonly root;
25
+ private readonly source;
26
+ private readonly nowNs;
27
+ private readonly byId;
28
+ private readonly byFile;
29
+ private synced;
30
+ /** Bumped by every binding change made outside a sync; a sync that saw one never vouches for the directory. */
31
+ private mutations;
32
+ private refreshing;
33
+ constructor(root: string, source: WakeAcceptanceIndexSource, nowNs?: () => bigint);
34
+ /**
35
+ * The record bound to this id, verified to still carry it. Unknown and
36
+ * compacted ids are polled, so a miss against an unchanged directory reads
37
+ * no record file.
38
+ */
39
+ find<T extends Readonly<{
40
+ acceptance_id: string;
41
+ }>>(acceptanceId: string, read: (file: string) => Promise<T | undefined>): Promise<T | undefined>;
42
+ /** Binds a record this process wrote; its file stamp is learned on the next sync. */
43
+ set(acceptanceId: string, file: string): void;
44
+ /** Drops a record file this process deleted. */
45
+ forget(file: string): void;
46
+ /** Brings the index level with the directory; a no-op while the directory is unchanged. Single-flight. */
47
+ refresh(): Promise<void>;
48
+ private refreshNow;
49
+ private scan;
50
+ /** A binding found stale: drop only what still says so, never a newer binding of the same file. */
51
+ private unbind;
52
+ private drop;
53
+ private bind;
54
+ }
55
+ //# sourceMappingURL=wakeAcceptanceIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wakeAcceptanceIndex.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeAcceptanceIndex.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE3G,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,iDAAiD;IACjD,aAAa,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjD,2EAA2E;IAC3E,KAAK,EAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IACxC,iFAAiF;IACjF,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,oEAAoE;IACpE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;CACrE,CAAC,CAAC;AAEH;;;;;GAKG;AACH,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;IAA6B,OAAO,CAAC,QAAQ,CAAC,KAAK;IANrH,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,MAAM,CAAqB;IACnC,+GAA+G;IAC/G,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAA4B;gBACjB,IAAI,EAAE,MAAM,EAAmB,MAAM,EAAE,yBAAyB,EAAmB,KAAK,GAAE,MAAM,MAA8C;IAC3K;;;;OAIG;IACG,IAAI,CAAC,CAAC,SAAS,QAAQ,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAYvJ,qFAAqF;IACrF,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK7C,gDAAgD;IAChD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAC1B,0GAA0G;IACpG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAMhB,UAAU;YAiBV,IAAI;IAiBlB,mGAAmG;IACnG,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,IAAI;CAMb"}
@@ -0,0 +1,158 @@
1
+ import path from "node:path";
2
+ import { MAX_WAKE_ACCEPTANCE_RECORDS } from "./wakeAcceptanceRetention.js";
3
+ /**
4
+ * A stamp younger than this may share a coarse filesystem timestamp tick with
5
+ * a write that lands after it was taken, so it is never trusted to prove that
6
+ * nothing changed (the racy-timestamp rule).
7
+ */
8
+ const RACY_WINDOW_NS = 2000000000n;
9
+ /**
10
+ * Own writes bind without a sync, so another process compacting behind a host
11
+ * that only ever hits could leave bindings for files that are gone. Past this
12
+ * many the index is dropped and the next miss rebuilds it from the directory.
13
+ */
14
+ const MAX_INDEX_ENTRIES = MAX_WAKE_ACCEPTANCE_RECORDS * 2;
15
+ /**
16
+ * Acceptance-id → record-file index. Own writes keep it current; a directory
17
+ * whose stamp moved since the last sync (another process, or this one) is
18
+ * re-listed, and only record files whose own stamp moved are read again. A miss
19
+ * against an unchanged directory reads no record file.
20
+ */
21
+ export class WakeAcceptanceIndex {
22
+ root;
23
+ source;
24
+ nowNs;
25
+ byId = new Map();
26
+ byFile = new Map();
27
+ synced;
28
+ /** Bumped by every binding change made outside a sync; a sync that saw one never vouches for the directory. */
29
+ mutations = 0;
30
+ refreshing;
31
+ constructor(root, source, nowNs = () => BigInt(Date.now()) * 1000000n) {
32
+ this.root = root;
33
+ this.source = source;
34
+ this.nowNs = nowNs;
35
+ }
36
+ /**
37
+ * The record bound to this id, verified to still carry it. Unknown and
38
+ * compacted ids are polled, so a miss against an unchanged directory reads
39
+ * no record file.
40
+ */
41
+ async find(acceptanceId, read) {
42
+ for (let attempt = 0; attempt < 2; attempt += 1) {
43
+ const known = this.byId.get(acceptanceId);
44
+ if (known !== undefined) {
45
+ const record = await read(known);
46
+ if (record?.acceptance_id === acceptanceId)
47
+ return record;
48
+ this.unbind(acceptanceId, known);
49
+ }
50
+ if (attempt === 0)
51
+ await this.refresh();
52
+ }
53
+ return undefined;
54
+ }
55
+ /** Binds a record this process wrote; its file stamp is learned on the next sync. */
56
+ set(acceptanceId, file) {
57
+ this.mutations += 1;
58
+ if (this.byFile.size >= MAX_INDEX_ENTRIES) {
59
+ this.byFile.clear();
60
+ this.byId.clear();
61
+ this.synced = undefined;
62
+ }
63
+ this.bind(file, acceptanceId, undefined);
64
+ }
65
+ /** Drops a record file this process deleted. */
66
+ forget(file) { this.mutations += 1; this.drop(file); }
67
+ /** Brings the index level with the directory; a no-op while the directory is unchanged. Single-flight. */
68
+ async refresh() {
69
+ while (this.refreshing !== undefined)
70
+ await this.refreshing.catch(() => undefined);
71
+ const run = this.refreshNow();
72
+ this.refreshing = run;
73
+ try {
74
+ await run;
75
+ }
76
+ finally {
77
+ if (this.refreshing === run)
78
+ this.refreshing = undefined;
79
+ }
80
+ }
81
+ async refreshNow() {
82
+ const startedAt = this.nowNs();
83
+ const directory = await this.source.directoryStat();
84
+ const directoryStamp = stampOf(directory);
85
+ if (this.synced === directoryStamp)
86
+ return;
87
+ const mutations = this.mutations;
88
+ const listed = (await this.source.files()).map((name) => path.join(this.root, name));
89
+ // Prune what the directory no longer lists even when a record below fails to
90
+ // read, so the maps stay bounded by the store's own record bound.
91
+ const present = new Set(listed);
92
+ try {
93
+ await this.scan(listed, present);
94
+ }
95
+ finally {
96
+ for (const file of [...this.byFile.keys()])
97
+ if (!present.has(file))
98
+ this.drop(file);
99
+ }
100
+ // Vouch for the directory only if nothing rebound while listing and its stamp
101
+ // was already settled when it was taken (the racy-timestamp rule).
102
+ this.synced = this.mutations === mutations && !racy(directory, startedAt) ? directoryStamp : undefined;
103
+ }
104
+ async scan(listed, present) {
105
+ for (const file of listed) {
106
+ const seenAt = this.nowNs();
107
+ const stat = await this.source.fileStat(file);
108
+ if (stat === undefined) {
109
+ present.delete(file);
110
+ continue;
111
+ }
112
+ const stamp = racy(stat, seenAt) ? undefined : stampOf(stat);
113
+ const known = this.byFile.get(file);
114
+ if (known !== undefined && known.stamp !== undefined && known.stamp === stamp)
115
+ continue;
116
+ let id;
117
+ try {
118
+ id = await this.source.readId(file);
119
+ }
120
+ catch (error) {
121
+ if (error.code !== "ENOENT")
122
+ throw error;
123
+ present.delete(file);
124
+ continue;
125
+ }
126
+ this.bind(file, id, stamp);
127
+ }
128
+ }
129
+ /** A binding found stale: drop only what still says so, never a newer binding of the same file. */
130
+ unbind(acceptanceId, file) {
131
+ this.mutations += 1;
132
+ if (this.byId.get(acceptanceId) === file)
133
+ this.byId.delete(acceptanceId);
134
+ if (this.byFile.get(file)?.id === acceptanceId)
135
+ this.byFile.delete(file);
136
+ this.synced = undefined;
137
+ }
138
+ drop(file) {
139
+ const entry = this.byFile.get(file);
140
+ if (entry !== undefined && this.byId.get(entry.id) === file)
141
+ this.byId.delete(entry.id);
142
+ this.byFile.delete(file);
143
+ this.synced = undefined;
144
+ }
145
+ bind(file, acceptanceId, stamp) {
146
+ const prior = this.byFile.get(file);
147
+ if (prior !== undefined && prior.id !== acceptanceId && this.byId.get(prior.id) === file)
148
+ this.byId.delete(prior.id);
149
+ this.byFile.set(file, { id: acceptanceId, ...(stamp === undefined ? {} : { stamp }) });
150
+ this.byId.set(acceptanceId, file);
151
+ }
152
+ }
153
+ /** A stamp taken within the window of its own last change may share a tick with a later write. */
154
+ function racy(stat, observedAt) {
155
+ const changed = stat.mtimeNs > stat.ctimeNs ? stat.mtimeNs : stat.ctimeNs;
156
+ return observedAt - changed < RACY_WINDOW_NS;
157
+ }
158
+ function stampOf(stat) { return `${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`; }
@@ -7,11 +7,19 @@ export type TransitionLock = Readonly<{
7
7
  pid_namespace_dev: number;
8
8
  pid_namespace_ino: number;
9
9
  }>;
10
+ type BlockedLockDetail = Readonly<{
11
+ step: string;
12
+ acceptance_id: string;
13
+ expected: Omit<TransitionLock, "owner_id" | "generation">;
14
+ current: TransitionLock;
15
+ }>;
10
16
  export declare class WakeTransitionLockBlockedError extends Error {
17
+ readonly detail?: BlockedLockDetail | undefined;
11
18
  readonly code: "offline_reconciliation_required";
12
- constructor();
19
+ constructor(detail?: BlockedLockDetail | undefined);
13
20
  }
14
21
  export declare function sameNamespace(left: Pick<TransitionLock, "pid_namespace_dev" | "pid_namespace_ino">, right: Pick<TransitionLock, "pid_namespace_dev" | "pid_namespace_ino">): boolean;
15
22
  export declare function currentProcessIdentity(): Promise<Omit<TransitionLock, "owner_id" | "generation">>;
16
23
  export declare function processIsAlive(lock: TransitionLock): Promise<boolean>;
24
+ export {};
17
25
  //# sourceMappingURL=wakeAcceptanceProcessIdentity.d.ts.map