@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
@@ -0,0 +1,143 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import path from "node:path";
4
+ import { WAKE_MOMENTS } from "../contracts/wakeMomentContract.js";
5
+ import { cleanMomentPaths, openMomentDirectory, openMomentFile, pinMomentStore, removeMomentPath, syncMomentDirectory, writeMomentBytes } from "./wakeMomentFs.js";
6
+ import { WakeMomentIo } from "./wakeMomentIo.js";
7
+ import { momentLines } from "./wakeMomentRows.js";
8
+ import { completeSnapshotNames, eligibleSnapshot, indexFromNames, latestSnapshotIndex, snapshotRootName, snapshotTime, writeSnapshotIndex } from "./wakeMomentHistory.js";
9
+ const sweeps = new Map();
10
+ const key = (snapshot) => `${snapshot.id}/${snapshot.snapshot}`;
11
+ function expiredRow(line, agentId, cutoff, current) {
12
+ try {
13
+ const row = JSON.parse(line.toString("utf8"));
14
+ if (current && row.execution_id === current.execution_id && row.started_at === current.started_at)
15
+ return undefined;
16
+ if (row?.version === WAKE_MOMENTS.version && row.agent_id === agentId && typeof row.execution_id === "string"
17
+ && Array.isArray(row.deliveries) && Array.isArray(row.snapshots) && typeof row.started_at === "string"
18
+ && new Date(row.started_at).toISOString() === row.started_at && Date.parse(row.started_at) < cutoff)
19
+ return row;
20
+ }
21
+ catch { /* Unknown/partial lines are not ours to delete. */ }
22
+ return undefined;
23
+ }
24
+ /** First-line age check; a full sweep every 64 calls also handles foreign prefixes/clock rollback. */
25
+ export async function pruneWakeMoments(recording, agentId, now = Date.now(), current, scope, authority) {
26
+ const io = scope ?? new WakeMomentIo();
27
+ const store = authority ?? await pinMomentStore(recording.directory, io);
28
+ io.verifyWrite = (target) => store.verify(io, target);
29
+ try {
30
+ const cutoff = now - recording.keepMs;
31
+ await pruneSnapshots(recording.directory, new Set(current?.snapshots.map(key) ?? []), cutoff, io, store);
32
+ await pruneRows(recording, agentId, cutoff, current, io, store);
33
+ }
34
+ finally {
35
+ if (!authority)
36
+ await store.close();
37
+ }
38
+ }
39
+ async function pruneRows(recording, agentId, cutoff, current, io, store) {
40
+ const target = path.join(recording.directory, WAKE_MOMENTS.rowsFile);
41
+ let source;
42
+ try {
43
+ source = await openMomentFile(target, constants.O_RDONLY, io);
44
+ }
45
+ catch (error) {
46
+ if (error.code === "ENOENT")
47
+ return;
48
+ throw error;
49
+ }
50
+ const temporary = `${target}.${randomUUID()}.tmp`;
51
+ let output, changed = false, created = false;
52
+ try {
53
+ const count = (sweeps.get(target) ?? 0) + 1;
54
+ sweeps.set(target, count % 64);
55
+ let firstExpired = false;
56
+ for await (const line of momentLines(source)) {
57
+ firstExpired = expiredRow(line, agentId, cutoff, current) !== undefined;
58
+ break;
59
+ }
60
+ if (!firstExpired && count % 64 !== 0)
61
+ return;
62
+ output = await openMomentFile(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, io);
63
+ created = true;
64
+ const reread = await openMomentFile(target, constants.O_RDONLY, io);
65
+ try {
66
+ for await (const line of momentLines(reread)) {
67
+ const expired = expiredRow(line, agentId, cutoff, current);
68
+ if (!expired) {
69
+ await writeMomentBytes(output, line);
70
+ continue;
71
+ }
72
+ changed = true;
73
+ }
74
+ }
75
+ finally {
76
+ await reread.close();
77
+ }
78
+ if (!changed)
79
+ return;
80
+ await output.sync();
81
+ await output.close();
82
+ output = undefined;
83
+ await io.fs.rename(temporary, target);
84
+ created = false;
85
+ await syncMomentDirectory(recording.directory, io);
86
+ }
87
+ finally {
88
+ await source.close();
89
+ await output?.close();
90
+ if (created)
91
+ await removeMomentPath(temporary, store, io);
92
+ }
93
+ }
94
+ /** Age comes from names; ownership comes from the writer's zero-byte completion marker. */
95
+ async function pruneSnapshots(directory, protectedSnapshots, cutoff, io, store) {
96
+ const namespace = path.join(directory, WAKE_MOMENTS.snapshotsDirectory);
97
+ try {
98
+ const ns = await openMomentDirectory(namespace, io);
99
+ await ns.close();
100
+ for await (const root of await io.fs.opendir(namespace)) {
101
+ if (!root.isDirectory() || !/^(?:[A-Za-z0-9._-]{1,128}|%2E|%2E%2E)$/.test(root.name))
102
+ continue;
103
+ const id = root.name === "%2E" ? "." : root.name === "%2E%2E" ? ".." : root.name;
104
+ if (snapshotRootName(id) !== root.name)
105
+ continue;
106
+ const base = path.join(namespace, root.name);
107
+ const fd = await openMomentDirectory(base, io);
108
+ await fd.close();
109
+ const state = await latestSnapshotIndex(base, io);
110
+ if (!state.names && (state.index.oldest === null || snapshotTime(state.index.oldest) >= cutoff))
111
+ continue;
112
+ const names = state.names ?? await completeSnapshotNames(base, io), removed = new Set();
113
+ for (const name of names.slice(1)) {
114
+ if (protectedSnapshots.has(`${id}/${name}`) || snapshotTime(name) >= cutoff)
115
+ continue;
116
+ const target = path.join(base, name), manifest = `${target}.manifest.json`, marker = `${target}${WAKE_MOMENTS.completionSuffix}`;
117
+ await store.verify(io, target);
118
+ if (!await eligibleSnapshot(base, name, io))
119
+ continue;
120
+ await store.verify(io, target);
121
+ await removeMomentPath(target, store, io);
122
+ await removeMomentPath(manifest, store, io);
123
+ await removeMomentPath(marker, store, io);
124
+ await syncMomentDirectory(base, io);
125
+ removed.add(name);
126
+ }
127
+ const index = indexFromNames(names.filter((name) => !removed.has(name)));
128
+ if (index) {
129
+ const cleanup = new Set();
130
+ try {
131
+ await writeSnapshotIndex(base, index, io, cleanup);
132
+ }
133
+ finally {
134
+ await cleanMomentPaths(cleanup, store, io);
135
+ }
136
+ }
137
+ }
138
+ }
139
+ catch (error) {
140
+ if (error.code !== "ENOENT")
141
+ throw error;
142
+ }
143
+ }
@@ -0,0 +1,8 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ import { WakeMomentIo } from "./wakeMomentIo.js";
3
+ import type { WakeMomentRow } from "./wakeMomentRecorder.js";
4
+ /** One append write and fsync. A torn prior append is delimited, never mistaken for this attempt. */
5
+ export declare function appendWakeMomentRow(directory: string, row: WakeMomentRow, io?: WakeMomentIo): Promise<void>;
6
+ /** Bound memory even when a damaged/foreign rows file has no newlines. */
7
+ export declare function momentLines(fd: FileHandle): AsyncGenerator<Buffer>;
8
+ //# sourceMappingURL=wakeMomentRows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wakeMomentRows.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeMomentRows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,qGAAqG;AACrG,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBvH;AAED,0EAA0E;AAC1E,wBAAuB,WAAW,CAAC,EAAE,EAAE,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,CAmBzE"}
@@ -0,0 +1,66 @@
1
+ import { constants } from "node:fs";
2
+ import path from "node:path";
3
+ import { WAKE_MOMENTS } from "../contracts/wakeMomentContract.js";
4
+ import { openMomentFile, syncMomentDirectory, WakeMomentFault } from "./wakeMomentFs.js";
5
+ import { WakeMomentIo } from "./wakeMomentIo.js";
6
+ /** One append write and fsync. A torn prior append is delimited, never mistaken for this attempt. */
7
+ export async function appendWakeMomentRow(directory, row, io = new WakeMomentIo()) {
8
+ const line = Buffer.from(`${JSON.stringify(row)}\n`);
9
+ if (line.length > WAKE_MOMENTS.maxRowBytes)
10
+ throw new WakeMomentFault("row_size_limit");
11
+ const target = path.join(directory, WAKE_MOMENTS.rowsFile);
12
+ let created = false, fd;
13
+ try {
14
+ fd = await openMomentFile(target, constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_APPEND, io);
15
+ created = true;
16
+ }
17
+ catch (error) {
18
+ if (error.code !== "EEXIST")
19
+ throw error;
20
+ fd = await openMomentFile(target, constants.O_RDWR | constants.O_APPEND, io);
21
+ }
22
+ try {
23
+ const size = (await fd.stat()).size;
24
+ const tail = Buffer.alloc(1);
25
+ if (size)
26
+ await fd.read(tail, 0, 1, size - 1);
27
+ const bytes = size && tail[0] !== 10 ? Buffer.concat([Buffer.from("\n"), line]) : line;
28
+ const { bytesWritten } = await fd.write(bytes);
29
+ if (bytesWritten !== bytes.length)
30
+ throw new WakeMomentFault("short_row_write");
31
+ await fd.sync();
32
+ if (created)
33
+ await syncMomentDirectory(directory, io);
34
+ }
35
+ finally {
36
+ await fd.close();
37
+ }
38
+ }
39
+ /** Bound memory even when a damaged/foreign rows file has no newlines. */
40
+ export async function* momentLines(fd) {
41
+ const chunk = Buffer.alloc(64 * 1024);
42
+ let pending = [], length = 0;
43
+ for (;;) {
44
+ const { bytesRead } = await fd.read(chunk, 0, chunk.length, null);
45
+ if (!bytesRead)
46
+ break;
47
+ let start = 0;
48
+ while (start < bytesRead) {
49
+ const newline = chunk.indexOf(10, start);
50
+ const end = newline >= start && newline < bytesRead ? newline + 1 : bytesRead;
51
+ const part = Buffer.from(chunk.subarray(start, end));
52
+ length += part.length;
53
+ if (length > WAKE_MOMENTS.maxRowBytes)
54
+ throw new WakeMomentFault("row_size_limit");
55
+ pending.push(part);
56
+ if (part[part.length - 1] === 10) {
57
+ yield Buffer.concat(pending, length);
58
+ pending = [];
59
+ length = 0;
60
+ }
61
+ start = end;
62
+ }
63
+ }
64
+ if (length)
65
+ yield Buffer.concat(pending, length);
66
+ }
@@ -0,0 +1,56 @@
1
+ import { WAKE_MOMENTS } from "../contracts/wakeMomentContract.js";
2
+ import { WakeMomentIo } from "./wakeMomentIo.js";
3
+ export { completeSnapshotNames, snapshotName, snapshotRootName, snapshotTime } from "./wakeMomentHistory.js";
4
+ type Identity = {
5
+ dev: string;
6
+ ino: string;
7
+ size: string;
8
+ mtimeNs: string;
9
+ ctimeNs: string;
10
+ mode: number;
11
+ };
12
+ export type SnapshotEntry = {
13
+ type: "file" | "directory" | "symlink";
14
+ mode: number;
15
+ size: string;
16
+ source: Identity;
17
+ target?: string;
18
+ };
19
+ export type WakeSnapshotManifest = {
20
+ version: typeof WAKE_MOMENTS.version;
21
+ id: string;
22
+ snapshot: string;
23
+ started_at: string;
24
+ sourcePath: string;
25
+ entries: Record<string, SnapshotEntry>;
26
+ };
27
+ export type WakeSnapshot = {
28
+ id: string;
29
+ snapshot: string;
30
+ files: number;
31
+ copied: number;
32
+ linked: number;
33
+ skipped: number;
34
+ };
35
+ export declare function readSnapshotManifest(directory: string, name: string, id: string, io?: WakeMomentIo, maxManifestBytes?: number): Promise<WakeSnapshotManifest | undefined>;
36
+ /** SOURCE identity, never the copied inode's metadata: ordinary writers cannot restore ctime. */
37
+ export declare function sameSnapshotSource(left: Identity, right: Identity): boolean;
38
+ type CaptureInput = {
39
+ directory: string;
40
+ root: {
41
+ id: string;
42
+ path: string;
43
+ };
44
+ startedAt: string;
45
+ executionId: string;
46
+ /** Internal test bounds; not caller configuration. */
47
+ maxEntries?: number;
48
+ maxManifestBytes?: number;
49
+ io?: WakeMomentIo;
50
+ cleanup?: Set<string>;
51
+ };
52
+ export declare function captureWakeSnapshot(input: CaptureInput): Promise<{
53
+ snapshot: WakeSnapshot;
54
+ note?: string;
55
+ }>;
56
+ //# sourceMappingURL=wakeMomentSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wakeMomentSnapshot.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeMomentSnapshot.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAIlE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE7G,KAAK,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC3G,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACtI,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,OAAO,YAAY,CAAC,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAC3G,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC,CAAC;AACF,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAC5H,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,eAAqB,EAAE,gBAAgB,GAAoC,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAQtN;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAG3E;AAKD,KAAK,YAAY,GAAG;IAClB,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAC9F,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC/C,EAAE,CAAC,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBjH"}
@@ -0,0 +1,248 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import path from "node:path";
4
+ import { WAKE_MOMENTS } from "../contracts/wakeMomentContract.js";
5
+ import { assertNoSymlinkComponents } from "./physicalReadiness.js";
6
+ import { cleanMomentPaths, ensureMomentDirectory, manifestBytes, momentPathExists, openMomentFile, pinMomentStore, readMomentJson, syncMomentDirectory, WakeMomentFault, writeMomentJson } from "./wakeMomentFs.js";
7
+ import { WakeMomentIo } from "./wakeMomentIo.js";
8
+ import { indexWithSnapshot, latestSnapshotIndex, snapshotName, snapshotRootName, snapshotTime, writeSnapshotIndex } from "./wakeMomentHistory.js";
9
+ export { completeSnapshotNames, snapshotName, snapshotRootName, snapshotTime } from "./wakeMomentHistory.js";
10
+ export async function readSnapshotManifest(directory, name, id, io = new WakeMomentIo(), maxManifestBytes = WAKE_MOMENTS.maxManifestBytes) {
11
+ try {
12
+ const value = await readMomentJson(path.join(directory, `${name}.manifest.json`), io, maxManifestBytes);
13
+ if (!value || value.version !== WAKE_MOMENTS.version || value.id !== id || value.snapshot !== name || Date.parse(value.started_at) !== snapshotTime(name)
14
+ || typeof value.sourcePath !== "string" || !value.entries || typeof value.entries !== "object" || Array.isArray(value.entries)
15
+ || Object.keys(value.entries).length > WAKE_MOMENTS.maxSnapshotEntries)
16
+ return undefined;
17
+ return value;
18
+ }
19
+ catch {
20
+ io.check();
21
+ return undefined;
22
+ }
23
+ }
24
+ /** SOURCE identity, never the copied inode's metadata: ordinary writers cannot restore ctime. */
25
+ export function sameSnapshotSource(left, right) {
26
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size
27
+ && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs && left.mode === right.mode;
28
+ }
29
+ function identity(stat) {
30
+ return { dev: String(stat.dev), ino: String(stat.ino), size: String(stat.size), mtimeNs: String(stat.mtimeNs), ctimeNs: String(stat.ctimeNs), mode: Number(stat.mode) };
31
+ }
32
+ export async function captureWakeSnapshot(input) {
33
+ if (input.io && input.cleanup)
34
+ return capture(input, input.io, input.cleanup);
35
+ const io = new WakeMomentIo(), cleanup = new Set();
36
+ let store;
37
+ try {
38
+ return await io.bounded(WAKE_MOMENTS.captureDeadlineMs, async () => {
39
+ store = await pinMomentStore(input.directory, io);
40
+ io.verifyWrite = (target) => store.verify(io, target);
41
+ return capture(input, io, cleanup);
42
+ });
43
+ }
44
+ finally {
45
+ if (store) {
46
+ const finalizer = new WakeMomentIo();
47
+ finalizer.verifyWrite = (target) => store.verify(finalizer, target);
48
+ try {
49
+ await finalizer.bounded(WAKE_MOMENTS.finalizationDeadlineMs, () => cleanMomentPaths(cleanup, store, finalizer));
50
+ }
51
+ finally {
52
+ void store.close().catch(() => undefined);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ async function capture(input, io, cleanup) {
58
+ const namespace = path.join(input.directory, WAKE_MOMENTS.snapshotsDirectory);
59
+ await ensureMomentDirectory(namespace, io);
60
+ const base = path.join(namespace, snapshotRootName(input.root.id));
61
+ await ensureMomentDirectory(base, io);
62
+ const requestedName = snapshotName(input.startedAt, input.executionId);
63
+ let name = requestedName;
64
+ while (await collides(name))
65
+ name = `${requestedName}-${randomUUID()}`;
66
+ const partial = path.join(base, `${name}.partial`), destination = path.join(base, name);
67
+ const { index } = await latestSnapshotIndex(base, io, cleanup), previousName = index?.snapshot;
68
+ let previous = previousName ? await readSnapshotManifest(base, previousName, input.root.id, io, input.maxManifestBytes) : undefined;
69
+ if (previous?.sourcePath !== input.root.path)
70
+ previous = undefined;
71
+ const manifest = { version: WAKE_MOMENTS.version, id: input.root.id, snapshot: name, started_at: input.startedAt, sourcePath: input.root.path, entries: Object.create(null) };
72
+ const snapshot = { id: input.root.id, snapshot: name, files: 0, copied: 0, linked: 0, skipped: 0 };
73
+ const max = Math.min(input.maxEntries ?? WAKE_MOMENTS.maxSnapshotEntries, WAKE_MOMENTS.maxSnapshotEntries);
74
+ let entries = 0, note;
75
+ await io.fs.mkdir(partial, { mode: 0o700 });
76
+ cleanup.add(partial);
77
+ {
78
+ let root;
79
+ try {
80
+ await assertNoSymlinkComponents(input.root.path, io.fs.lstat);
81
+ root = await openSourceDirectory(input.root.path, io);
82
+ }
83
+ catch (error) {
84
+ if (error.code === "ENOENT")
85
+ note = "root_missing";
86
+ else
87
+ throw error;
88
+ }
89
+ if (root) {
90
+ try {
91
+ await walk(root, input.root.path, "");
92
+ }
93
+ finally {
94
+ await root.close();
95
+ }
96
+ }
97
+ // The writer enforces exactly the reader's byte bound BEFORE either publication rename.
98
+ const bytes = manifestBytes(manifest, input.maxManifestBytes);
99
+ await syncMomentDirectory(partial, io);
100
+ // Reserve the final name exclusively before rename can replace anything at it.
101
+ await io.fs.mkdir(destination, { mode: 0o700 });
102
+ cleanup.add(destination);
103
+ await io.fs.rename(partial, destination);
104
+ cleanup.delete(partial);
105
+ await syncMomentDirectory(base, io);
106
+ const manifestPath = path.join(base, `${name}.manifest.json`);
107
+ await writeMomentJson(manifestPath, bytes, io, cleanup);
108
+ try {
109
+ await writeSnapshotIndex(base, indexWithSnapshot(index, name), io, cleanup);
110
+ }
111
+ catch (error) {
112
+ if (!(error instanceof WakeMomentFault) || error.reason !== "invalid_latest_index")
113
+ throw error;
114
+ note = [note, error.reason].filter(Boolean).join("; ");
115
+ }
116
+ const markerPath = path.join(base, `${name}${WAKE_MOMENTS.completionSuffix}`);
117
+ const marker = await openMomentFile(markerPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, io);
118
+ cleanup.add(markerPath);
119
+ try {
120
+ await marker.sync();
121
+ }
122
+ finally {
123
+ await marker.close();
124
+ }
125
+ await syncMomentDirectory(base, io);
126
+ cleanup.delete(markerPath);
127
+ cleanup.delete(manifestPath);
128
+ cleanup.delete(destination);
129
+ return { snapshot, ...(note ? { note } : {}) };
130
+ }
131
+ async function collides(candidate) {
132
+ for (const suffix of ["", ".partial", ".manifest.json", WAKE_MOMENTS.completionSuffix]) {
133
+ if (await momentPathExists(path.join(base, `${candidate}${suffix}`), io))
134
+ return true;
135
+ }
136
+ return false; // Temporary metadata names are separately checked and exclusively created.
137
+ }
138
+ async function walk(directory, source, relative) {
139
+ // Linux's descriptor-relative paths pin ancestors against a concurrent rename/symlink swap.
140
+ const anchored = process.platform === "linux" ? `/proc/self/fd/${directory.fd}` : source;
141
+ const expected = await directory.stat({ bigint: true });
142
+ await checkSourceDirectory(directory, source, expected, io);
143
+ for await (const entry of await io.fs.opendir(anchored)) {
144
+ await checkSourceDirectory(directory, source, expected, io);
145
+ if (++entries > max)
146
+ throw new WakeMomentFault("snapshot_entry_limit");
147
+ const rel = relative ? `${relative}/${entry.name}` : entry.name;
148
+ const from = path.join(anchored, entry.name), to = path.join(partial, rel);
149
+ const stat = await io.fs.lstat(from, { bigint: true }), sourceIdentity = identity(stat);
150
+ await checkSourceDirectory(directory, source, expected, io);
151
+ if (stat.isDirectory()) {
152
+ const child = await openSourceDirectory(from, io, stat);
153
+ try {
154
+ await io.fs.mkdir(to, { mode: 0o700 });
155
+ manifest.entries[rel] = { type: "directory", mode: Number(stat.mode), size: String(stat.size), source: sourceIdentity };
156
+ await walk(child, from, rel);
157
+ await syncMomentDirectory(to, io);
158
+ }
159
+ finally {
160
+ await child.close();
161
+ }
162
+ }
163
+ else if (stat.isSymbolicLink()) {
164
+ const target = await io.fs.readlink(from);
165
+ await io.fs.symlink(target, to);
166
+ manifest.entries[rel] = { type: "symlink", mode: Number(stat.mode), size: String(stat.size), source: sourceIdentity, target };
167
+ }
168
+ else if (stat.isFile()) {
169
+ const sourceFile = await io.fs.open(from, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
170
+ try {
171
+ if (!sameSnapshotSource(sourceIdentity, identity(await sourceFile.stat({ bigint: true }))))
172
+ throw new WakeMomentFault("source_changed");
173
+ await checkSourceDirectory(directory, source, expected, io);
174
+ const prior = previous && Object.hasOwn(previous.entries, rel) ? previous.entries[rel] : undefined;
175
+ if (prior?.type === "file" && prior.source && sameSnapshotSource(prior.source, sourceIdentity)) {
176
+ const old = path.join(base, previous.snapshot, rel);
177
+ await assertNoSymlinkComponents(path.dirname(old), io.fs.lstat);
178
+ if (!(await io.fs.lstat(old)).isFile())
179
+ throw new WakeMomentFault("invalid_link_base");
180
+ await io.fs.link(old, to);
181
+ snapshot.linked++;
182
+ }
183
+ else {
184
+ await copyFile(sourceFile, to, stat, io);
185
+ snapshot.copied++;
186
+ }
187
+ if (!sameSnapshotSource(sourceIdentity, identity(await sourceFile.stat({ bigint: true }))))
188
+ throw new WakeMomentFault("source_changed");
189
+ manifest.entries[rel] = { type: "file", mode: Number(stat.mode), size: String(stat.size), source: sourceIdentity };
190
+ snapshot.files++;
191
+ }
192
+ finally {
193
+ await sourceFile.close();
194
+ }
195
+ }
196
+ else
197
+ snapshot.skipped++;
198
+ }
199
+ await checkSourceDirectory(directory, source, expected, io);
200
+ }
201
+ }
202
+ async function openSourceDirectory(source, io, before) {
203
+ if (process.platform !== "linux" || !source.startsWith("/proc/self/fd/"))
204
+ await assertNoSymlinkComponents(source, io.fs.lstat);
205
+ before ??= await io.fs.lstat(source, { bigint: true });
206
+ const fd = await io.fs.open(source, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
207
+ try {
208
+ await checkSourceDirectory(fd, source, before, io);
209
+ return fd;
210
+ }
211
+ catch (error) {
212
+ await fd.close();
213
+ throw error;
214
+ }
215
+ }
216
+ async function checkSourceDirectory(directory, source, before, io) {
217
+ const opened = await directory.stat({ bigint: true }), current = await io.fs.lstat(source, { bigint: true });
218
+ if (![before, opened, current].every((stat) => stat.isDirectory() && stat.dev === before.dev && stat.ino === before.ino)) {
219
+ throw new WakeMomentFault("source_changed");
220
+ }
221
+ }
222
+ async function copyFile(source, target, stat, io) {
223
+ if (stat.size > BigInt(Number.MAX_SAFE_INTEGER))
224
+ throw new WakeMomentFault("source_size_limit");
225
+ const output = await io.fs.open(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
226
+ try {
227
+ const buffer = Buffer.alloc(1024 * 1024);
228
+ let position = 0;
229
+ while (position < Number(stat.size)) {
230
+ const { bytesRead } = await source.read(buffer, 0, Math.min(buffer.length, Number(stat.size) - position), position);
231
+ if (!bytesRead)
232
+ throw new WakeMomentFault("source_changed");
233
+ let written = 0;
234
+ while (written < bytesRead) {
235
+ const result = await output.write(buffer, written, bytesRead - written, position + written);
236
+ if (!result.bytesWritten)
237
+ throw new WakeMomentFault("short_write");
238
+ written += result.bytesWritten;
239
+ }
240
+ position += bytesRead;
241
+ }
242
+ await output.chmod(Number(stat.mode) & 0o777);
243
+ await output.sync();
244
+ }
245
+ finally {
246
+ await output.close();
247
+ }
248
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noopolis/daimon",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Noopolis native per-agent runtime harness built on Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "git+ssh://git@github.com/noopolis/daimon.git"
36
+ "url": "git+ssh://git@github.com/noopolis/daimon-harness.git"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
@@ -44,7 +44,7 @@
44
44
  "clean": "rm -rf dist",
45
45
  "prepack": "npm run build",
46
46
  "prepublishOnly": "npm run typecheck && npm test",
47
- "typecheck": "tsc --project tsconfig.json --noEmit && tsc --project scripts/tsconfig.json --noEmit",
47
+ "typecheck": "tsc --project tsconfig.json --noEmit && tsc --project scripts/tsconfig.json --noEmit && tsc --project .github/scripts/ci/tsconfig.json",
48
48
  "verify:contract-manifest": "node --import tsx scripts/emitRuntimeContractManifest.ts --check",
49
49
  "verify:native": "node --experimental-strip-types src/runtime/native/verifyArtifacts.ts",
50
50
  "verify:production-closure": "node --experimental-strip-types scripts/verifyProductionClosure.ts",
@@ -65,7 +65,7 @@
65
65
  "@earendil-works/pi-ai": "^0.79.10",
66
66
  "@earendil-works/pi-coding-agent": "^0.79.10",
67
67
  "@modelcontextprotocol/sdk": "^1.29.0",
68
- "@noopolis/mneme": "^0.1.1",
68
+ "@noopolis/mneme": "^0.1.3",
69
69
  "ajv": "^8.17.1",
70
70
  "zod": "^4.4.3"
71
71
  },