@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.
- package/README.md +6 -0
- package/dist/contracts/attentionContract.d.ts +25 -0
- package/dist/contracts/attentionContract.d.ts.map +1 -1
- package/dist/contracts/attentionContract.js +8 -1
- package/dist/contracts/grokBrokerTaskClock.d.ts +18 -0
- package/dist/contracts/grokBrokerTaskClock.d.ts.map +1 -0
- package/dist/contracts/grokBrokerTaskClock.js +16 -0
- package/dist/contracts/organizationRuntimeContract.d.ts +76 -0
- package/dist/contracts/organizationRuntimeContract.d.ts.map +1 -1
- package/dist/contracts/organizationRuntimeContract.js +2 -0
- package/dist/contracts/runtimeContractManifest.d.ts +222 -15
- package/dist/contracts/runtimeContractManifest.d.ts.map +1 -1
- package/dist/contracts/runtimeContractManifest.js +35 -10
- package/dist/contracts/shellEnvironmentPolicy.d.ts +3 -0
- package/dist/contracts/shellEnvironmentPolicy.d.ts.map +1 -0
- package/dist/contracts/shellEnvironmentPolicy.js +5 -0
- package/dist/contracts/wakeMomentContract.d.ts +61 -0
- package/dist/contracts/wakeMomentContract.d.ts.map +1 -0
- package/dist/contracts/wakeMomentContract.js +27 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.d.ts +1 -0
- package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.js +6 -2
- package/dist/observability/causalEvents.d.ts +3 -3
- package/dist/observability/causalEvents.d.ts.map +1 -1
- package/dist/observability/causalEvents.js +4 -4
- package/dist/pi/cliEngineSpawn.d.ts +2 -2
- package/dist/pi/cliEngineSpawn.d.ts.map +1 -1
- package/dist/pi/cliEngineSpawn.js +7 -2
- package/dist/pi/cliEnvironment.d.ts.map +1 -1
- package/dist/pi/cliEnvironment.js +2 -0
- package/dist/pi/cliMcpRegistration.d.ts +1 -1
- package/dist/pi/cliMcpRegistration.d.ts.map +1 -1
- package/dist/pi/cliMcpRegistration.js +1 -1
- package/dist/pi/cliSession.d.ts +2 -0
- package/dist/pi/cliSession.d.ts.map +1 -1
- package/dist/pi/cliSession.js +3 -80
- package/dist/pi/cliSessionMcp.d.ts +9 -0
- package/dist/pi/cliSessionMcp.d.ts.map +1 -0
- package/dist/pi/cliSessionMcp.js +80 -0
- package/dist/pi/engineTaskClock.d.ts +9 -0
- package/dist/pi/engineTaskClock.d.ts.map +1 -0
- package/dist/pi/engineTaskClock.js +28 -0
- package/dist/pi/grokHomeMcpRegistration.d.ts.map +1 -1
- package/dist/pi/grokHomeMcpRegistration.js +2 -1
- package/dist/pi/grokSandbox.js +1 -1
- package/dist/pi/memoryClock.d.ts +25 -0
- package/dist/pi/memoryClock.d.ts.map +1 -0
- package/dist/pi/memoryClock.js +18 -0
- package/dist/pi/piAgentHandle.d.ts +3 -1
- package/dist/pi/piAgentHandle.d.ts.map +1 -1
- package/dist/pi/piAgentHandle.js +5 -2
- package/dist/pi/piHarness.d.ts +8 -2
- package/dist/pi/piHarness.d.ts.map +1 -1
- package/dist/pi/piHarness.js +28 -12
- package/dist/pi/taskClockProcess.d.ts +6 -0
- package/dist/pi/taskClockProcess.d.ts.map +1 -0
- package/dist/pi/taskClockProcess.js +8 -0
- package/dist/pi/turnCausal.d.ts +4 -2
- package/dist/pi/turnCausal.d.ts.map +1 -1
- package/dist/pi/turnCausal.js +4 -4
- package/dist/pi/wakeAcceptance.d.ts +1 -1
- package/dist/pi/wakeAcceptance.d.ts.map +1 -1
- package/dist/pi/wakeAcceptance.js +2 -2
- package/dist/runtime/agySubscriptionBootstrap.js +1 -1
- package/dist/runtime/agySubscriptionRealm.js +1 -1
- package/dist/runtime/attention.d.ts +5 -0
- package/dist/runtime/attention.d.ts.map +1 -1
- package/dist/runtime/attention.js +14 -1
- package/dist/runtime/attentionDispatcher.d.ts +14 -2
- package/dist/runtime/attentionDispatcher.d.ts.map +1 -1
- package/dist/runtime/attentionDispatcher.js +127 -33
- package/dist/runtime/cli.js +5 -0
- package/dist/runtime/contract-manifest.json +1 -1
- package/dist/runtime/contract-manifest.sha256 +1 -1
- package/dist/runtime/engineBrokerCapabilities.d.ts +10 -0
- package/dist/runtime/engineBrokerCapabilities.d.ts.map +1 -1
- package/dist/runtime/engineBrokerCapabilities.js +10 -0
- package/dist/runtime/engineBrokerControlClient.d.ts +12 -1
- package/dist/runtime/engineBrokerControlClient.d.ts.map +1 -1
- package/dist/runtime/engineBrokerControlClient.js +139 -43
- package/dist/runtime/engineBrokerCredentialJournal.d.ts +2 -0
- package/dist/runtime/engineBrokerCredentialJournal.d.ts.map +1 -1
- package/dist/runtime/engineBrokerCredentialJournal.js +9 -0
- package/dist/runtime/engineBrokerInferenceProtocol.d.ts +3 -2
- package/dist/runtime/engineBrokerInferenceProtocol.d.ts.map +1 -1
- package/dist/runtime/engineBrokerInferenceProtocol.js +4 -4
- package/dist/runtime/engineBrokerMcpCallLog.d.ts +11 -0
- package/dist/runtime/engineBrokerMcpCallLog.d.ts.map +1 -1
- package/dist/runtime/engineBrokerMcpCallLog.js +13 -1
- package/dist/runtime/engineBrokerMcpFacade.d.ts +5 -2
- package/dist/runtime/engineBrokerMcpFacade.d.ts.map +1 -1
- package/dist/runtime/engineBrokerMcpFacade.js +5 -2
- package/dist/runtime/engineBrokerNativeClient.js +2 -2
- package/dist/runtime/engineBrokerProtocol.d.ts +42 -10
- package/dist/runtime/engineBrokerProtocol.d.ts.map +1 -1
- package/dist/runtime/engineBrokerProtocol.js +97 -14
- package/dist/runtime/engineBrokerSealLedger.d.ts.map +1 -1
- package/dist/runtime/engineBrokerSealLedger.js +9 -0
- package/dist/runtime/engineBrokerService.d.ts +8 -2
- package/dist/runtime/engineBrokerService.d.ts.map +1 -1
- package/dist/runtime/engineBrokerService.js +95 -37
- package/dist/runtime/engineBrokerServiceCli.js +1 -1
- package/dist/runtime/engineBrokerServiceConfig.d.ts +5 -0
- package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
- package/dist/runtime/engineBrokerServiceConfig.js +47 -9
- package/dist/runtime/engineBrokerTurnRegistry.d.ts +1 -1
- package/dist/runtime/engineBrokerTurnRegistry.d.ts.map +1 -1
- package/dist/runtime/engineBrokerTurnRegistry.js +7 -6
- package/dist/runtime/engineDispatcher.d.ts +2 -0
- package/dist/runtime/engineDispatcher.d.ts.map +1 -1
- package/dist/runtime/engineDispatcher.js +14 -2
- package/dist/runtime/engineReadiness.js +1 -1
- package/dist/runtime/grokBrokerCredentialAuthority.d.ts +1 -0
- package/dist/runtime/grokBrokerCredentialAuthority.d.ts.map +1 -1
- package/dist/runtime/grokBrokerCredentialAuthority.js +48 -22
- package/dist/runtime/grokBrokerCredentialReader.d.ts +1 -0
- package/dist/runtime/grokBrokerCredentialReader.d.ts.map +1 -1
- package/dist/runtime/grokBrokerCredentialReader.js +2 -1
- package/dist/runtime/grokBrokerProjection.d.ts +9 -1
- package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProjection.js +11 -4
- package/dist/runtime/grokBrokerProviderClock.d.ts +26 -0
- package/dist/runtime/grokBrokerProviderClock.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProviderClock.js +133 -0
- package/dist/runtime/grokBrokerProxy.d.ts +17 -3
- package/dist/runtime/grokBrokerProxy.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProxy.js +73 -9
- package/dist/runtime/grokBrokerProxyJson.d.ts +10 -0
- package/dist/runtime/grokBrokerProxyJson.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProxyJson.js +31 -0
- package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProxyRequest.js +17 -6
- package/dist/runtime/grokBrokerRealmLease.js +1 -1
- package/dist/runtime/grokBrokerRefresh.js +1 -1
- package/dist/runtime/grokBrokerRunBudget.d.ts +20 -0
- package/dist/runtime/grokBrokerRunBudget.d.ts.map +1 -0
- package/dist/runtime/grokBrokerRunBudget.js +85 -0
- package/dist/runtime/grokBrokerSlot.d.ts +6 -0
- package/dist/runtime/grokBrokerSlot.d.ts.map +1 -0
- package/dist/runtime/grokBrokerSlot.js +21 -0
- package/dist/runtime/grokBrokerTaskClock.d.ts +32 -0
- package/dist/runtime/grokBrokerTaskClock.d.ts.map +1 -0
- package/dist/runtime/grokBrokerTaskClock.js +120 -0
- package/dist/runtime/grokBrokerTurnMeter.d.ts +55 -3
- package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -1
- package/dist/runtime/grokBrokerTurnMeter.js +90 -4
- package/dist/runtime/grokBrokerWorkerConfig.d.ts +5 -2
- package/dist/runtime/grokBrokerWorkerConfig.d.ts.map +1 -1
- package/dist/runtime/grokBrokerWorkerConfig.js +22 -3
- package/dist/runtime/grokEngineBroker.d.ts +43 -3
- package/dist/runtime/grokEngineBroker.d.ts.map +1 -1
- package/dist/runtime/grokEngineBroker.js +159 -71
- package/dist/runtime/grokEngineBrokerTurn.d.ts +32 -8
- package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -1
- package/dist/runtime/grokEngineBrokerTurn.js +204 -21
- package/dist/runtime/grokEngineBrokerTurnEnd.d.ts +88 -0
- package/dist/runtime/grokEngineBrokerTurnEnd.d.ts.map +1 -0
- package/dist/runtime/grokEngineBrokerTurnEnd.js +148 -0
- package/dist/runtime/grokInferenceProxy.d.ts.map +1 -1
- package/dist/runtime/grokInferenceProxy.js +3 -0
- package/dist/runtime/grokSubscriptionRealm.js +1 -1
- package/dist/runtime/grokWorkerAttestation.d.ts +9 -4
- package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerAttestation.js +2 -2
- package/dist/runtime/grokWorkerHomeAttestation.d.ts +4 -2
- package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerHomeAttestation.js +10 -4
- package/dist/runtime/index.d.ts +7 -2
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -1
- package/dist/runtime/native/daimon-engine-broker +0 -0
- package/dist/runtime/organizationRuntime.d.ts +3 -0
- package/dist/runtime/organizationRuntime.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeAdmission.d.ts +6 -0
- package/dist/runtime/organizationRuntimeAdmission.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeAdmission.js +22 -0
- package/dist/runtime/organizationRuntimeControl.d.ts +9 -0
- package/dist/runtime/organizationRuntimeControl.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeControl.js +72 -24
- package/dist/runtime/organizationRuntimeHost.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeHost.js +13 -4
- package/dist/runtime/organizationRuntimeParsing.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeParsing.js +4 -1
- package/dist/runtime/organizationRuntimeReadiness.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeReadiness.js +8 -0
- package/dist/runtime/organizationRuntimeRecording.d.ts +7 -0
- package/dist/runtime/organizationRuntimeRecording.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeRecording.js +58 -0
- package/dist/runtime/physicalReadiness.d.ts +12 -0
- package/dist/runtime/physicalReadiness.d.ts.map +1 -1
- package/dist/runtime/physicalReadiness.js +43 -9
- package/dist/runtime/productionAgentTools.d.ts.map +1 -1
- package/dist/runtime/productionAgentTools.js +13 -5
- package/dist/runtime/relocateRuntimeConfig.d.ts +14 -0
- package/dist/runtime/relocateRuntimeConfig.d.ts.map +1 -0
- package/dist/runtime/relocateRuntimeConfig.js +38 -0
- package/dist/runtime/schedule.d.ts.map +1 -1
- package/dist/runtime/schedule.js +12 -7
- package/dist/runtime/storeCoordination.d.ts +10 -0
- package/dist/runtime/storeCoordination.d.ts.map +1 -1
- package/dist/runtime/storeCoordination.js +23 -0
- package/dist/runtime/taskClock.d.ts +42 -0
- package/dist/runtime/taskClock.d.ts.map +1 -0
- package/dist/runtime/taskClock.js +102 -0
- package/dist/runtime/taskClockProcess.d.ts +17 -0
- package/dist/runtime/taskClockProcess.d.ts.map +1 -0
- package/dist/runtime/taskClockProcess.js +58 -0
- package/dist/runtime/turnRequestLedger.d.ts +0 -5
- package/dist/runtime/turnRequestLedger.d.ts.map +1 -1
- package/dist/runtime/turnRequestLedger.js +23 -21
- package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts +34 -0
- package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceClaimDiagnostics.js +79 -0
- package/dist/runtime/wakeAcceptanceIndex.d.ts +55 -0
- package/dist/runtime/wakeAcceptanceIndex.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceIndex.js +158 -0
- package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts +9 -1
- package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceProcessIdentity.js +5 -1
- package/dist/runtime/wakeAcceptanceRecord.d.ts +2 -0
- package/dist/runtime/wakeAcceptanceRecord.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceRecord.js +4 -2
- package/dist/runtime/wakeAcceptanceRetention.d.ts +25 -1
- package/dist/runtime/wakeAcceptanceRetention.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceRetention.js +28 -2
- package/dist/runtime/wakeAcceptanceStore.d.ts +8 -8
- package/dist/runtime/wakeAcceptanceStore.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceStore.js +66 -63
- package/dist/runtime/wakeFuse.d.ts +58 -3
- package/dist/runtime/wakeFuse.d.ts.map +1 -1
- package/dist/runtime/wakeFuse.js +106 -7
- package/dist/runtime/wakeMomentFs.d.ts +27 -0
- package/dist/runtime/wakeMomentFs.d.ts.map +1 -0
- package/dist/runtime/wakeMomentFs.js +223 -0
- package/dist/runtime/wakeMomentHistory.d.ts +26 -0
- package/dist/runtime/wakeMomentHistory.d.ts.map +1 -0
- package/dist/runtime/wakeMomentHistory.js +109 -0
- package/dist/runtime/wakeMomentIo.d.ts +22 -0
- package/dist/runtime/wakeMomentIo.d.ts.map +1 -0
- package/dist/runtime/wakeMomentIo.js +114 -0
- package/dist/runtime/wakeMomentRecorder.d.ts +33 -0
- package/dist/runtime/wakeMomentRecorder.d.ts.map +1 -0
- package/dist/runtime/wakeMomentRecorder.js +81 -0
- package/dist/runtime/wakeMomentRetention.d.ts +7 -0
- package/dist/runtime/wakeMomentRetention.d.ts.map +1 -0
- package/dist/runtime/wakeMomentRetention.js +143 -0
- package/dist/runtime/wakeMomentRows.d.ts +8 -0
- package/dist/runtime/wakeMomentRows.d.ts.map +1 -0
- package/dist/runtime/wakeMomentRows.js +66 -0
- package/dist/runtime/wakeMomentSnapshot.d.ts +56 -0
- package/dist/runtime/wakeMomentSnapshot.d.ts.map +1 -0
- package/dist/runtime/wakeMomentSnapshot.js +248 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wakeFuse.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeFuse.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,iBAAiB,EAAG,8BAAuC,CAAC;AACzE,eAAO,MAAM,uBAAuB,EAAG,4BAAqC,CAAC;AAC7E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,4BAA4B,UAAY,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC/W,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,yBAAyB,GAAG,qBAAqB,CAAA;CAAE,CAAC,CAAC;AACtN,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;GAMG;AACH,qBAAa,QAAQ;IAMjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"wakeFuse.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeFuse.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,iBAAiB,EAAG,8BAAuC,CAAC;AACzE,eAAO,MAAM,uBAAuB,EAAG,4BAAqC,CAAC;AAC7E,eAAO,MAAM,wBAAwB,EAAG,6BAAsC,CAAC;AAC/E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,4BAA4B,UAAY,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC/W,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,yBAAyB,GAAG,qBAAqB,CAAA;CAAE,CAAC,CAAC;AACtN;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpF;AAED,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;GAMG;AACH,qBAAa,QAAQ;IAMjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAd5B,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO;WAaM,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IASnG;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAS7E,WAAW;IASnB,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAQjE,OAAO,IAAI,kBAAkB,GAAG,SAAS;IACnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAE5B;;;;;;;;;;;;;;;;;;;OAmBG;YACW,cAAc;YAyBd,QAAQ;YAuBR,IAAI;CAiBnB;AAED,wBAAgB,wBAAwB,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7F"}
|
package/dist/runtime/wakeFuse.js
CHANGED
|
@@ -5,8 +5,34 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
5
5
|
import { resolveTurnUsageLedgerPath, TURN_USAGE_LEDGER, TURN_USAGE_MAX_IDENTIFIER_CHARS, TURN_USAGE_ROTATE_BYTES } from "./turnUsageLedger.js";
|
|
6
6
|
export const WAKE_FUSE_VERSION = "noopolis.daimon.wake-fuse.v1";
|
|
7
7
|
export const WAKE_FUSE_DIRECTORY_ENV = "DAIMON_WAKE_FUSE_DIRECTORY";
|
|
8
|
+
export const WAKE_FUSE_EPOCH_ZONE_ENV = "DAIMON_WAKE_FUSE_EPOCH_ZONE";
|
|
8
9
|
export const DEFAULT_WAKE_FUSE_MAX_WAKES = 100;
|
|
9
10
|
export const DEFAULT_WAKE_FUSE_MAX_TOKENS = 5_000_000;
|
|
11
|
+
/**
|
|
12
|
+
* The counting window, when the operator has not named one.
|
|
13
|
+
*
|
|
14
|
+
* WHY THIS IS DERIVED AND NOT CONFIGURED
|
|
15
|
+
* --------------------------------------
|
|
16
|
+
* `DAIMON_WAKE_FUSE_EPOCH` is read from the container's env file, and an env
|
|
17
|
+
* file is applied at container CREATION only. So an operator who wants a daily
|
|
18
|
+
* budget has to rewrite that file and RECREATE THE CONTAINER every day, and
|
|
19
|
+
* the only thing that ever did that was a daily rebuild. The budget was
|
|
20
|
+
* therefore coupled to a deploy: stop deploying daily and the wake ceiling
|
|
21
|
+
* never resets, and the organization stops admitting wakes for good.
|
|
22
|
+
*
|
|
23
|
+
* A date-derived epoch removes the coupling instead of moving it. There is no
|
|
24
|
+
* file to rewrite, no deploy, no restart, and no clock for anybody to forget.
|
|
25
|
+
*
|
|
26
|
+
* An explicitly configured epoch still wins, unchanged: an operator pinning one
|
|
27
|
+
* window is doing it deliberately, and this must not roll it out from under
|
|
28
|
+
* them.
|
|
29
|
+
*/
|
|
30
|
+
export function derivedEpoch(organizationKey, at, zone) {
|
|
31
|
+
// `en-CA` renders ISO-8601 (YYYY-MM-DD), which is what makes this sortable
|
|
32
|
+
// and stable; the zone is what makes "a day" mean the operator's day.
|
|
33
|
+
const day = new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(at);
|
|
34
|
+
return `organization-${createHash("sha256").update(organizationKey).digest("hex")}-${day}`;
|
|
35
|
+
}
|
|
10
36
|
const TRIP_MARKER = "fuse.trip.json";
|
|
11
37
|
let warnedDisarmed = false;
|
|
12
38
|
/**
|
|
@@ -26,10 +52,11 @@ export class WakeFuse {
|
|
|
26
52
|
admissions;
|
|
27
53
|
usageLedgerPath;
|
|
28
54
|
now;
|
|
55
|
+
nextEpoch;
|
|
29
56
|
serial = Promise.resolve();
|
|
30
57
|
reason;
|
|
31
58
|
tripMarkerMissing = false;
|
|
32
|
-
constructor(armed, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now) {
|
|
59
|
+
constructor(armed, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now, nextEpoch) {
|
|
33
60
|
this.armed = armed;
|
|
34
61
|
this.directory = directory;
|
|
35
62
|
this.epoch = epoch;
|
|
@@ -39,6 +66,7 @@ export class WakeFuse {
|
|
|
39
66
|
this.admissions = admissions;
|
|
40
67
|
this.usageLedgerPath = usageLedgerPath;
|
|
41
68
|
this.now = now;
|
|
69
|
+
this.nextEpoch = nextEpoch;
|
|
42
70
|
}
|
|
43
71
|
static async open(options) {
|
|
44
72
|
const environment = options.environment ?? process.env;
|
|
@@ -47,20 +75,23 @@ export class WakeFuse {
|
|
|
47
75
|
throw new Error("DAIMON_WAKE_FUSE must be exactly 'off' when set");
|
|
48
76
|
if (environment.DAIMON_WAKE_FUSE_EPOCH !== undefined && nonBlank(environment.DAIMON_WAKE_FUSE_EPOCH) === undefined)
|
|
49
77
|
throw new Error("DAIMON_WAKE_FUSE_EPOCH must be non-blank");
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
const pinned = nonBlank(environment.DAIMON_WAKE_FUSE_EPOCH);
|
|
79
|
+
const zone = nonBlank(environment[WAKE_FUSE_EPOCH_ZONE_ENV]) ?? "UTC";
|
|
80
|
+
// A pinned epoch never rolls; a derived one rolls with the operator's day.
|
|
81
|
+
// Agent ids are the stable organization identity available to Daimon.
|
|
82
|
+
const nextEpoch = (at) => pinned ?? derivedEpoch(options.organizationKey, at, zone);
|
|
53
83
|
const maxWakes = ceiling(environment.DAIMON_WAKE_FUSE_MAX_WAKES, DEFAULT_WAKE_FUSE_MAX_WAKES, "DAIMON_WAKE_FUSE_MAX_WAKES");
|
|
54
84
|
const maxTokens = ceiling(environment.DAIMON_WAKE_FUSE_MAX_TOKENS, DEFAULT_WAKE_FUSE_MAX_TOKENS, "DAIMON_WAKE_FUSE_MAX_TOKENS");
|
|
55
85
|
const directory = resolveWakeFuseDirectory(environment);
|
|
56
86
|
const now = options.now ?? (() => new Date());
|
|
87
|
+
const epoch = nextEpoch(now());
|
|
57
88
|
const usageLedgerPath = resolveTurnUsageLedgerPath(environment);
|
|
58
89
|
if (setting === "off") {
|
|
59
90
|
if (!warnedDisarmed) {
|
|
60
91
|
warnedDisarmed = true;
|
|
61
92
|
console.error("DAIMON WAKE FUSE IS OFF: wake admission is unbounded");
|
|
62
93
|
}
|
|
63
|
-
return new WakeFuse(false, directory, epoch, now().toISOString(), maxWakes, maxTokens, new Set(), usageLedgerPath, now);
|
|
94
|
+
return new WakeFuse(false, directory, epoch, now().toISOString(), maxWakes, maxTokens, new Set(), usageLedgerPath, now, nextEpoch);
|
|
64
95
|
}
|
|
65
96
|
// Unbounded defaults would reproduce exactly the failure this module prevents.
|
|
66
97
|
await readdir(directory);
|
|
@@ -73,7 +104,7 @@ export class WakeFuse {
|
|
|
73
104
|
const admissions = new Set(records
|
|
74
105
|
.filter((record) => record.kind === "admission" && record.epoch === epoch)
|
|
75
106
|
.map((record) => key(record.agent, record.delivery)));
|
|
76
|
-
const fuse = new WakeFuse(true, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now);
|
|
107
|
+
const fuse = new WakeFuse(true, directory, epoch, epochStartedAt, maxWakes, maxTokens, admissions, usageLedgerPath, now, nextEpoch);
|
|
77
108
|
if (await exists(path.join(directory, "fuse.stop")))
|
|
78
109
|
fuse.reason = "operator_stop";
|
|
79
110
|
else
|
|
@@ -89,7 +120,27 @@ export class WakeFuse {
|
|
|
89
120
|
this.serial = result.then(() => undefined, () => undefined);
|
|
90
121
|
return result;
|
|
91
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* The budget as the caller should act on it NOW — which means after the day
|
|
125
|
+
* has been allowed to roll.
|
|
126
|
+
*
|
|
127
|
+
* This is the gate the scheduler actually consults: `AttentionDispatcher.drain()`
|
|
128
|
+
* returns at `snapshot().state !== "available"` before it ever reaches
|
|
129
|
+
* `admit()`. Rolling only inside `admitNow` therefore never fired for an
|
|
130
|
+
* organization that had hit its ceiling — it stayed paused across midnight
|
|
131
|
+
* and only a process restart could renew the budget. The roll is serialized
|
|
132
|
+
* with admissions so a snapshot and an admission never see two windows.
|
|
133
|
+
*/
|
|
92
134
|
async snapshot(agentId, limits = {}) {
|
|
135
|
+
const result = this.serial.catch(() => undefined).then(async () => {
|
|
136
|
+
if (this.armed)
|
|
137
|
+
await this.rollEpochIfDue();
|
|
138
|
+
return await this.snapshotNow(agentId, limits);
|
|
139
|
+
});
|
|
140
|
+
this.serial = result.then(() => undefined, () => undefined);
|
|
141
|
+
return await result;
|
|
142
|
+
}
|
|
143
|
+
async snapshotNow(agentId, limits = {}) {
|
|
93
144
|
const used = this.admissions.size;
|
|
94
145
|
const agentUsed = [...this.admissions].filter((entry) => entry.startsWith(`${bounded(agentId)}\u0000`)).length;
|
|
95
146
|
const tokens = this.armed ? await sumTokens(this.usageLedgerPath, this.epochStartedAt) : 0;
|
|
@@ -111,7 +162,55 @@ export class WakeFuse {
|
|
|
111
162
|
}
|
|
112
163
|
tripped() { return this.reason; }
|
|
113
164
|
async close() { await this.serial; }
|
|
165
|
+
/**
|
|
166
|
+
* Moves the counting window when the derived day has changed.
|
|
167
|
+
*
|
|
168
|
+
* Called at admission, which is the only moment the answer can matter: the
|
|
169
|
+
* organization runtime is a long-lived process, so an epoch computed once at
|
|
170
|
+
* open() would be the same epoch a week later.
|
|
171
|
+
*
|
|
172
|
+
* WHAT ROLLS AND WHAT DOES NOT
|
|
173
|
+
* ----------------------------
|
|
174
|
+
* A new window clears a CEILING trip, exactly as selecting a new epoch by
|
|
175
|
+
* hand always did: `readTripMarker` only honours a marker belonging to the
|
|
176
|
+
* current epoch. It does NOT clear an operator stop. `fuse.stop` is checked
|
|
177
|
+
* by existence and is deliberately not epoch-scoped, so a parked newsroom
|
|
178
|
+
* stays parked across midnight. That asymmetry is the whole point: a budget
|
|
179
|
+
* is a thing that renews, an operator stop is a decision that does not.
|
|
180
|
+
*
|
|
181
|
+
* Admissions are rebuilt from the ledger rather than emptied, so a clock that
|
|
182
|
+
* steps BACKWARD into a window that already spent wakes does not hand the
|
|
183
|
+
* organization a fresh budget.
|
|
184
|
+
*/
|
|
185
|
+
async rollEpochIfDue() {
|
|
186
|
+
const wanted = this.nextEpoch(this.now());
|
|
187
|
+
if (wanted === this.epoch)
|
|
188
|
+
return;
|
|
189
|
+
const records = await readFuseRecords(this.directory);
|
|
190
|
+
const existingStart = records.filter((record) => record.kind === "epoch_start" && record.epoch === wanted).at(-1);
|
|
191
|
+
const startedAt = existingStart?.at ?? this.now().toISOString();
|
|
192
|
+
if (existingStart === undefined)
|
|
193
|
+
await append(this.directory, { v: WAKE_FUSE_VERSION, kind: "epoch_start", epoch: wanted, at: startedAt });
|
|
194
|
+
this.epoch = wanted;
|
|
195
|
+
this.epochStartedAt = startedAt;
|
|
196
|
+
this.admissions = new Set(records
|
|
197
|
+
.filter((record) => record.kind === "admission" && record.epoch === wanted)
|
|
198
|
+
.map((record) => key(record.agent, record.delivery)));
|
|
199
|
+
// Re-evaluate the trip for the NEW window. Clearing a CEILING is the part
|
|
200
|
+
// that does work here, and it is what makes the budget renew.
|
|
201
|
+
//
|
|
202
|
+
// The operator-stop branch is deliberately BELT AND BRACES, not the guard:
|
|
203
|
+
// `admitNow` checks `fuse.stop` itself on every admission, unscoped by
|
|
204
|
+
// epoch, and `trip()` sets the reason — so a park is already enforced
|
|
205
|
+
// whatever this line does. I could not write a failing test for it, and
|
|
206
|
+
// said so rather than dressing it up as a guarantee. It stays because it
|
|
207
|
+
// keeps the object's own state coherent at the moment it rolls; the test
|
|
208
|
+
// above pins the BEHAVIOUR, which is what must not regress.
|
|
209
|
+
this.reason = await exists(path.join(this.directory, "fuse.stop")) ? "operator_stop" : await readTripMarker(this.directory, wanted);
|
|
210
|
+
}
|
|
114
211
|
async admitNow(agentId, deliveryId, limits) {
|
|
212
|
+
// Before the trip is consulted: a new day may have cleared a ceiling.
|
|
213
|
+
await this.rollEpochIfDue();
|
|
115
214
|
if (this.reason !== undefined)
|
|
116
215
|
return await this.trip(this.reason);
|
|
117
216
|
try {
|
|
@@ -120,7 +219,7 @@ export class WakeFuse {
|
|
|
120
219
|
const admissionKey = key(bounded(agentId), bounded(deliveryId));
|
|
121
220
|
if (this.admissions.has(admissionKey))
|
|
122
221
|
return { state: "admitted" };
|
|
123
|
-
const budget = await this.
|
|
222
|
+
const budget = await this.snapshotNow(agentId, limits);
|
|
124
223
|
if (budget.agent_executions_remaining === 0)
|
|
125
224
|
return { state: "paused", reason: "agent_execution_ceiling" };
|
|
126
225
|
if (budget.agent_tokens_remaining === 0)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type FileHandle } from "node:fs/promises";
|
|
2
|
+
import { WakeMomentIo } from "./wakeMomentIo.js";
|
|
3
|
+
export { WakeMomentFault } from "./wakeMomentIo.js";
|
|
4
|
+
/** Diagnostics never include source paths, file contents, or arbitrary exception text. */
|
|
5
|
+
export declare function wakeMomentFailure(error: unknown): string;
|
|
6
|
+
export type MomentStore = {
|
|
7
|
+
verify(io: WakeMomentIo, target?: string): Promise<void>;
|
|
8
|
+
close(): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
/** Pin the caller's root, then re-check the same dev/ino/uid/mode at every write boundary. */
|
|
11
|
+
export declare function pinMomentStore(directory: string, io: WakeMomentIo, verifyPrepared?: () => Promise<void>): Promise<MomentStore>;
|
|
12
|
+
export declare function openMomentDirectory(directory: string, io?: WakeMomentIo): Promise<FileHandle>;
|
|
13
|
+
export declare function ensureMomentDirectory(directory: string, io?: WakeMomentIo): Promise<void>;
|
|
14
|
+
export declare function syncMomentDirectory(directory: string, io?: WakeMomentIo): Promise<void>;
|
|
15
|
+
/** Refuses symlinks, special files and hard-linked metadata, including a FIFO before it can block. */
|
|
16
|
+
export declare function openMomentFile(file: string, flags?: number, io?: WakeMomentIo): Promise<FileHandle>;
|
|
17
|
+
export declare function manifestBytes(value: unknown, max?: number): string;
|
|
18
|
+
export declare function readMomentJson(file: string, io?: WakeMomentIo, max?: number): Promise<unknown>;
|
|
19
|
+
/** Unlike FileHandle.writeFile, each syscall is admitted through the cancellation fence. */
|
|
20
|
+
export declare function writeMomentBytes(fd: FileHandle, bytes: string | Buffer): Promise<void>;
|
|
21
|
+
export declare function writeMomentJson(file: string, bytes: string, io: WakeMomentIo, cleanup: Set<string>, verifyReplacement?: () => Promise<void>): Promise<void>;
|
|
22
|
+
export declare function momentPathExists(target: string, io?: WakeMomentIo): Promise<boolean>;
|
|
23
|
+
/** Never recurse until root identity AND the target's parent components have just been verified. */
|
|
24
|
+
export declare function cleanMomentPaths(paths: Set<string>, store: MomentStore, io: WakeMomentIo): Promise<void>;
|
|
25
|
+
/** Explicit traversal keeps recursive cleanup cancellable between individual filesystem calls. */
|
|
26
|
+
export declare function removeMomentPath(target: string, store: MomentStore, io: WakeMomentIo): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=wakeMomentFs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeMomentFs.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeMomentFs.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKzD,OAAO,EAAmB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIxD;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAE/G,8FAA8F;AAC9F,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BpI;AAMD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAKzG;AAED,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrG;AAED,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnG;AAED,sGAAsG;AACtG,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAqB,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAO3H;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAoC,MAAM,GAAG,MAAM,CAInG;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,eAAqB,EAAE,GAAG,GAAoC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiB3I;AAED,4FAA4F;AAC5F,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5F;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjK;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,eAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhG;AAED,oGAAoG;AACpG,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9G;AAED,kGAAkG;AAClG,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAc1G"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { WAKE_MOMENTS } from "../contracts/wakeMomentContract.js";
|
|
6
|
+
import { assertNoSymlinkComponents, assertRuntimeDirectory, identity, sameIdentity } from "./physicalReadiness.js";
|
|
7
|
+
import { WakeMomentFault, WakeMomentIo } from "./wakeMomentIo.js";
|
|
8
|
+
export { WakeMomentFault } from "./wakeMomentIo.js";
|
|
9
|
+
/** Diagnostics never include source paths, file contents, or arbitrary exception text. */
|
|
10
|
+
export function wakeMomentFailure(error) {
|
|
11
|
+
if (error instanceof WakeMomentFault)
|
|
12
|
+
return error.reason;
|
|
13
|
+
const code = error?.code;
|
|
14
|
+
return ["EACCES", "EPERM", "ENOENT", "ENOSPC", "EIO", "ELOOP", "ENOTDIR", "EEXIST", "EMFILE", "ENAMETOOLONG", "EXDEV"].includes(code ?? "") ? code : "recording_io_failed";
|
|
15
|
+
}
|
|
16
|
+
/** Pin the caller's root, then re-check the same dev/ino/uid/mode at every write boundary. */
|
|
17
|
+
export async function pinMomentStore(directory, io, verifyPrepared) {
|
|
18
|
+
await assertNoSymlinkComponents(directory, io.fs.lstat);
|
|
19
|
+
const before = await io.fs.lstat(directory);
|
|
20
|
+
assertPrivate(before);
|
|
21
|
+
// A raw handle is shared by the capture and separately bounded finalization scopes.
|
|
22
|
+
const fd = await io.call("pinStore", () => open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW));
|
|
23
|
+
const expected = identity(before);
|
|
24
|
+
const store = {
|
|
25
|
+
async verify(scope, target) {
|
|
26
|
+
try {
|
|
27
|
+
await scope.call("verifyPreparedStore", async () => { await verifyPrepared?.(); });
|
|
28
|
+
await assertNoSymlinkComponents(directory, scope.fs.lstat);
|
|
29
|
+
const entry = await scope.fs.lstat(directory);
|
|
30
|
+
const opened = await scope.call("statStore", () => fd.stat());
|
|
31
|
+
assertPrivate(entry);
|
|
32
|
+
if (!sameIdentity(expected, identity(entry)) || !sameIdentity(expected, identity(opened)))
|
|
33
|
+
throw new Error("changed");
|
|
34
|
+
if (target?.startsWith(`${directory}${path.sep}`))
|
|
35
|
+
await assertNoSymlinkComponents(path.dirname(target), scope.fs.lstat);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
scope.check();
|
|
39
|
+
throw new WakeMomentFault("recording_store_changed");
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
close: () => fd.close()
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
await store.verify(io);
|
|
46
|
+
return store;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
void fd.close().catch(() => undefined);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function assertPrivate(entry) {
|
|
54
|
+
assertRuntimeDirectory(entry, "recording.directory", "private", { uid: process.getuid?.() ?? -1, gid: process.getgid?.() ?? -1 });
|
|
55
|
+
}
|
|
56
|
+
export async function openMomentDirectory(directory, io = new WakeMomentIo()) {
|
|
57
|
+
await assertNoSymlinkComponents(directory, io.fs.lstat);
|
|
58
|
+
const fd = await io.fs.open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
59
|
+
try {
|
|
60
|
+
assertPrivate(await fd.stat());
|
|
61
|
+
return fd;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
await fd.close();
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function ensureMomentDirectory(directory, io = new WakeMomentIo()) {
|
|
69
|
+
let created = false;
|
|
70
|
+
try {
|
|
71
|
+
await io.fs.mkdir(directory, { mode: 0o700 });
|
|
72
|
+
created = true;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.code !== "EEXIST")
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
const fd = await openMomentDirectory(directory, io);
|
|
79
|
+
try {
|
|
80
|
+
await fd.sync();
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await fd.close();
|
|
84
|
+
}
|
|
85
|
+
if (created)
|
|
86
|
+
await syncMomentDirectory(path.dirname(directory), io);
|
|
87
|
+
}
|
|
88
|
+
export async function syncMomentDirectory(directory, io = new WakeMomentIo()) {
|
|
89
|
+
const fd = await io.fs.open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
90
|
+
try {
|
|
91
|
+
await fd.sync();
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await fd.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Refuses symlinks, special files and hard-linked metadata, including a FIFO before it can block. */
|
|
98
|
+
export async function openMomentFile(file, flags = constants.O_RDONLY, io = new WakeMomentIo()) {
|
|
99
|
+
const fd = await io.fs.open(file, flags | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
|
|
100
|
+
try {
|
|
101
|
+
const stat = await fd.stat();
|
|
102
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
103
|
+
throw new WakeMomentFault("invalid_metadata_file");
|
|
104
|
+
return fd;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
await fd.close();
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function manifestBytes(value, max = WAKE_MOMENTS.maxManifestBytes) {
|
|
112
|
+
const bytes = JSON.stringify(value);
|
|
113
|
+
if (Buffer.byteLength(bytes) > Math.min(max, WAKE_MOMENTS.maxManifestBytes))
|
|
114
|
+
throw new WakeMomentFault("manifest_size_limit");
|
|
115
|
+
return bytes;
|
|
116
|
+
}
|
|
117
|
+
export async function readMomentJson(file, io = new WakeMomentIo(), max = WAKE_MOMENTS.maxManifestBytes) {
|
|
118
|
+
const fd = await openMomentFile(file, constants.O_RDONLY, io);
|
|
119
|
+
try {
|
|
120
|
+
const limit = Math.min(max, WAKE_MOMENTS.maxManifestBytes);
|
|
121
|
+
if ((await fd.stat()).size > limit)
|
|
122
|
+
throw new WakeMomentFault("manifest_size_limit");
|
|
123
|
+
const chunks = [];
|
|
124
|
+
let length = 0;
|
|
125
|
+
for (;;) {
|
|
126
|
+
const buffer = Buffer.alloc(Math.min(64 * 1024, limit + 1 - length));
|
|
127
|
+
const { bytesRead } = await fd.read(buffer, 0, buffer.length, null);
|
|
128
|
+
if (!bytesRead)
|
|
129
|
+
break;
|
|
130
|
+
length += bytesRead;
|
|
131
|
+
if (length > limit)
|
|
132
|
+
throw new WakeMomentFault("manifest_size_limit");
|
|
133
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
134
|
+
}
|
|
135
|
+
return JSON.parse(Buffer.concat(chunks, length).toString("utf8"));
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await fd.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Unlike FileHandle.writeFile, each syscall is admitted through the cancellation fence. */
|
|
142
|
+
export async function writeMomentBytes(fd, bytes) {
|
|
143
|
+
const buffer = typeof bytes === "string" ? Buffer.from(bytes) : bytes;
|
|
144
|
+
for (let offset = 0; offset < buffer.length;) {
|
|
145
|
+
const { bytesWritten } = await fd.write(buffer, offset, Math.min(64 * 1024, buffer.length - offset), null);
|
|
146
|
+
if (!bytesWritten)
|
|
147
|
+
throw new WakeMomentFault("short_write");
|
|
148
|
+
offset += bytesWritten;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export async function writeMomentJson(file, bytes, io, cleanup, verifyReplacement) {
|
|
152
|
+
let temporary;
|
|
153
|
+
do {
|
|
154
|
+
temporary = `${file}.${randomUUID()}.tmp`;
|
|
155
|
+
} while (await momentPathExists(temporary, io));
|
|
156
|
+
const fd = await openMomentFile(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, io);
|
|
157
|
+
cleanup.add(temporary); // Exclusive creation, never the name alone, establishes cleanup ownership.
|
|
158
|
+
try {
|
|
159
|
+
await writeMomentBytes(fd, bytes);
|
|
160
|
+
await fd.sync();
|
|
161
|
+
await fd.close();
|
|
162
|
+
let reservation;
|
|
163
|
+
try {
|
|
164
|
+
reservation = await openMomentFile(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, io);
|
|
165
|
+
cleanup.add(file);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
if (!verifyReplacement || error.code !== "EEXIST")
|
|
169
|
+
throw error;
|
|
170
|
+
await verifyReplacement(); // The caller must establish ownership before replacement.
|
|
171
|
+
reservation = await openMomentFile(file, constants.O_RDONLY, io);
|
|
172
|
+
}
|
|
173
|
+
await reservation.close();
|
|
174
|
+
await io.fs.rename(temporary, file);
|
|
175
|
+
cleanup.delete(temporary);
|
|
176
|
+
await syncMomentDirectory(path.dirname(file), io);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
await fd.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export async function momentPathExists(target, io = new WakeMomentIo()) {
|
|
183
|
+
try {
|
|
184
|
+
await io.fs.lstat(target);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (error.code === "ENOENT")
|
|
189
|
+
return false;
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Never recurse until root identity AND the target's parent components have just been verified. */
|
|
194
|
+
export async function cleanMomentPaths(paths, store, io) {
|
|
195
|
+
for (const target of paths) {
|
|
196
|
+
await removeMomentPath(target, store, io);
|
|
197
|
+
paths.delete(target);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Explicit traversal keeps recursive cleanup cancellable between individual filesystem calls. */
|
|
201
|
+
export async function removeMomentPath(target, store, io) {
|
|
202
|
+
await store.verify(io, target);
|
|
203
|
+
let entry;
|
|
204
|
+
try {
|
|
205
|
+
entry = await io.fs.lstat(target);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error.code === "ENOENT")
|
|
209
|
+
return;
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
if (entry.isDirectory()) {
|
|
213
|
+
await assertNoSymlinkComponents(target, io.fs.lstat);
|
|
214
|
+
for await (const child of await io.fs.opendir(target))
|
|
215
|
+
await removeMomentPath(path.join(target, child.name), store, io);
|
|
216
|
+
await store.verify(io, target);
|
|
217
|
+
await io.fs.rmdir(target);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
await store.verify(io, target);
|
|
221
|
+
await io.fs.unlink(target);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WakeMomentIo } from "./wakeMomentIo.js";
|
|
2
|
+
export declare const SNAPSHOT_NAME: RegExp;
|
|
3
|
+
export declare const snapshotRootName: (id: string) => string;
|
|
4
|
+
declare const INDEX_VERSION = "noopolis.daimon.wake-latest.v1";
|
|
5
|
+
export type SnapshotIndex = {
|
|
6
|
+
version: typeof INDEX_VERSION;
|
|
7
|
+
snapshot: string;
|
|
8
|
+
oldest: string | null;
|
|
9
|
+
};
|
|
10
|
+
export declare function snapshotName(startedAt: string, executionId: string): string;
|
|
11
|
+
export declare function snapshotTime(name: string): number;
|
|
12
|
+
/** Eligibility precedes BOTH base selection and retention protection, not only deletion. */
|
|
13
|
+
export declare function eligibleSnapshot(directory: string, name: string, io: WakeMomentIo, rollback?: ReadonlySet<string>): Promise<boolean>;
|
|
14
|
+
/** Only a missing/invalid index or an expired oldest name requires history enumeration. */
|
|
15
|
+
export declare function completeSnapshotNames(directory: string, io?: WakeMomentIo, rollback?: ReadonlySet<string>): Promise<string[]>;
|
|
16
|
+
export declare function indexFromNames(names: string[]): SnapshotIndex | undefined;
|
|
17
|
+
export declare function latestSnapshotIndex(directory: string, io: WakeMomentIo, rollback?: ReadonlySet<string>): Promise<{
|
|
18
|
+
index?: SnapshotIndex;
|
|
19
|
+
names?: string[];
|
|
20
|
+
}>;
|
|
21
|
+
/** Called after manifest publication, before the marker: a crash leaves an invalid index,
|
|
22
|
+
* never a valid stale pointer hiding a newer complete snapshot. Clock rollback keeps the newer base. */
|
|
23
|
+
export declare function indexWithSnapshot(previous: SnapshotIndex | undefined, name: string): SnapshotIndex;
|
|
24
|
+
export declare function writeSnapshotIndex(directory: string, index: SnapshotIndex, io: WakeMomentIo, cleanup: Set<string>): Promise<void>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=wakeMomentHistory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeMomentHistory.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeMomentHistory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAElE,eAAO,MAAM,aAAa,QAAyC,CAAC;AACpE,eAAO,MAAM,gBAAgB,GAAI,IAAI,MAAM,KAAG,MAAoE,CAAC;AACnH,QAAA,MAAM,aAAa,mCAAmC,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,OAAO,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEvG,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,4FAA4F;AAC5F,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAU1I;AAED,2FAA2F;AAC3F,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAqB,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAczI;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,GAAG,SAAS,CAEzE;AAUD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;IAAE,KAAK,CAAC,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAOnK;AAED;wGACwG;AACxG,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAMlG;AAED,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CASvI"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { WAKE_MOMENTS } from "../contracts/wakeMomentContract.js";
|
|
4
|
+
import { readMomentJson, writeMomentJson } from "./wakeMomentFs.js";
|
|
5
|
+
import { WakeMomentFault, WakeMomentIo } from "./wakeMomentIo.js";
|
|
6
|
+
export const SNAPSHOT_NAME = /^\d{8}T\d{9}Z-[A-Za-z0-9._-]{1,200}$/;
|
|
7
|
+
export const snapshotRootName = (id) => id === "." || id === ".." ? id.replaceAll(".", "%2E") : id;
|
|
8
|
+
const INDEX_VERSION = "noopolis.daimon.wake-latest.v1";
|
|
9
|
+
const INDEX_BYTES = 1024;
|
|
10
|
+
export function snapshotName(startedAt, executionId) {
|
|
11
|
+
const execution = /^[A-Za-z0-9._-]{1,128}$/.test(executionId) ? executionId : `sha256-${createHash("sha256").update(executionId).digest("hex")}`;
|
|
12
|
+
return `${startedAt.replace(/[-:.]/g, "")}-${execution}`;
|
|
13
|
+
}
|
|
14
|
+
export function snapshotTime(name) {
|
|
15
|
+
if (!SNAPSHOT_NAME.test(name))
|
|
16
|
+
return NaN;
|
|
17
|
+
const value = `${name.slice(0, 4)}-${name.slice(4, 6)}-${name.slice(6, 8)}T${name.slice(9, 11)}:${name.slice(11, 13)}:${name.slice(13, 15)}.${name.slice(15, 18)}Z`;
|
|
18
|
+
const time = Date.parse(value);
|
|
19
|
+
return Number.isFinite(time) && new Date(time).toISOString() === value ? time : NaN;
|
|
20
|
+
}
|
|
21
|
+
/** Eligibility precedes BOTH base selection and retention protection, not only deletion. */
|
|
22
|
+
export async function eligibleSnapshot(directory, name, io, rollback) {
|
|
23
|
+
if (!Number.isFinite(snapshotTime(name)))
|
|
24
|
+
return false;
|
|
25
|
+
const target = path.join(directory, name);
|
|
26
|
+
if ([target, `${target}.manifest.json`, `${target}${WAKE_MOMENTS.completionSuffix}`].some((file) => rollback?.has(file)))
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
const entry = await io.fs.lstat(target), manifest = await io.fs.lstat(`${target}.manifest.json`);
|
|
30
|
+
const marker = await io.fs.lstat(`${target}${WAKE_MOMENTS.completionSuffix}`);
|
|
31
|
+
return entry.isDirectory() && manifest.isFile() && manifest.nlink === 1
|
|
32
|
+
&& marker.isFile() && marker.size === 0 && marker.nlink === 1;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === "ENOENT")
|
|
36
|
+
return false;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Only a missing/invalid index or an expired oldest name requires history enumeration. */
|
|
41
|
+
export async function completeSnapshotNames(directory, io = new WakeMomentIo(), rollback) {
|
|
42
|
+
const names = [];
|
|
43
|
+
try {
|
|
44
|
+
for await (const entry of await io.fs.opendir(directory)) {
|
|
45
|
+
if (entry.isDirectory() && await eligibleSnapshot(directory, entry.name, io, rollback))
|
|
46
|
+
names.push(entry.name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error.code !== "ENOENT")
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
names.sort((a, b) => snapshotTime(b) - snapshotTime(a) || b.localeCompare(a));
|
|
54
|
+
// Timestamp ties use publication metadata; no historical manifest contents are read.
|
|
55
|
+
const stamps = new Map();
|
|
56
|
+
for (let i = 0; i < names.length; i++)
|
|
57
|
+
if (snapshotTime(names[i]) === snapshotTime(names[i - 1] ?? "") || snapshotTime(names[i]) === snapshotTime(names[i + 1] ?? "")) {
|
|
58
|
+
stamps.set(names[i], (await io.fs.lstat(path.join(directory, `${names[i]}.manifest.json`), { bigint: true })).mtimeNs);
|
|
59
|
+
}
|
|
60
|
+
return names.sort((a, b) => snapshotTime(b) - snapshotTime(a) || ((stamps.get(a) ?? 0n) < (stamps.get(b) ?? 0n) ? 1 : (stamps.get(a) ?? 0n) > (stamps.get(b) ?? 0n) ? -1 : b.localeCompare(a)));
|
|
61
|
+
}
|
|
62
|
+
export function indexFromNames(names) {
|
|
63
|
+
return names[0] ? { version: INDEX_VERSION, snapshot: names[0], oldest: names.length > 1 ? names[names.length - 1] : null } : undefined;
|
|
64
|
+
}
|
|
65
|
+
function isSnapshotIndex(value) {
|
|
66
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== 3)
|
|
67
|
+
return false;
|
|
68
|
+
const index = value;
|
|
69
|
+
return index.version === INDEX_VERSION && typeof index.snapshot === "string" && Number.isFinite(snapshotTime(index.snapshot))
|
|
70
|
+
&& (index.oldest === null || (typeof index.oldest === "string" && index.oldest !== index.snapshot
|
|
71
|
+
&& Number.isFinite(snapshotTime(index.oldest)) && snapshotTime(index.oldest) <= snapshotTime(index.snapshot)));
|
|
72
|
+
}
|
|
73
|
+
export async function latestSnapshotIndex(directory, io, rollback) {
|
|
74
|
+
try {
|
|
75
|
+
const value = await readMomentJson(path.join(directory, "latest"), io, INDEX_BYTES);
|
|
76
|
+
if (isSnapshotIndex(value) && await eligibleSnapshot(directory, value.snapshot, io, rollback))
|
|
77
|
+
return { index: value };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
io.check(); /* The bounded index is a cache; recover from missing/damaged metadata. */
|
|
81
|
+
}
|
|
82
|
+
const names = await completeSnapshotNames(directory, io, rollback);
|
|
83
|
+
return { index: indexFromNames(names), names };
|
|
84
|
+
}
|
|
85
|
+
/** Called after manifest publication, before the marker: a crash leaves an invalid index,
|
|
86
|
+
* never a valid stale pointer hiding a newer complete snapshot. Clock rollback keeps the newer base. */
|
|
87
|
+
export function indexWithSnapshot(previous, name) {
|
|
88
|
+
if (!previous)
|
|
89
|
+
return { version: INDEX_VERSION, snapshot: name, oldest: null };
|
|
90
|
+
const newest = snapshotTime(name) >= snapshotTime(previous.snapshot) ? name : previous.snapshot;
|
|
91
|
+
const others = [previous.oldest, newest === name ? previous.snapshot : name].filter((entry) => entry !== null);
|
|
92
|
+
others.sort((a, b) => snapshotTime(a) - snapshotTime(b));
|
|
93
|
+
return { version: INDEX_VERSION, snapshot: newest, oldest: others[0] };
|
|
94
|
+
}
|
|
95
|
+
export async function writeSnapshotIndex(directory, index, io, cleanup) {
|
|
96
|
+
const target = path.join(directory, "latest");
|
|
97
|
+
await writeMomentJson(target, JSON.stringify(index), io, cleanup, async () => {
|
|
98
|
+
let owned = false;
|
|
99
|
+
try {
|
|
100
|
+
owned = isSnapshotIndex(await readMomentJson(target, io, INDEX_BYTES));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
io.check();
|
|
104
|
+
}
|
|
105
|
+
if (!owned)
|
|
106
|
+
throw new WakeMomentFault("invalid_latest_index");
|
|
107
|
+
});
|
|
108
|
+
cleanup.delete(target);
|
|
109
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
export declare class WakeMomentFault extends Error {
|
|
3
|
+
readonly reason: string;
|
|
4
|
+
constructor(reason: string);
|
|
5
|
+
}
|
|
6
|
+
/** Internal fault-injection seam, never configuration or a public runtime export. */
|
|
7
|
+
export type WakeMomentIoProbe = (operation: string, target?: string, entries?: number) => void | Promise<void>;
|
|
8
|
+
/** Every filesystem call checks cancellation before issuing I/O and after it settles. */
|
|
9
|
+
export declare class WakeMomentIo {
|
|
10
|
+
private readonly probe?;
|
|
11
|
+
readonly fs: typeof fs;
|
|
12
|
+
verifyWrite?: (target?: string) => Promise<void>;
|
|
13
|
+
private aborted;
|
|
14
|
+
private expires;
|
|
15
|
+
constructor(probe?: WakeMomentIoProbe | undefined);
|
|
16
|
+
check(): void;
|
|
17
|
+
call<T>(operation: string, action: () => Promise<T>, target?: string): Promise<T>;
|
|
18
|
+
bounded<T>(milliseconds: number, action: () => Promise<T>): Promise<T>;
|
|
19
|
+
private handle;
|
|
20
|
+
private directory;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=wakeMomentIo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeMomentIo.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeMomentIo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAKvC,qBAAa,eAAgB,SAAQ,KAAK;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CACpC;AAED,qFAAqF;AACrF,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/G,yFAAyF;AACzF,qBAAa,YAAY;IAMX,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IALnC,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAY;gBAEE,KAAK,CAAC,EAAE,iBAAiB,YAAA;IAgBtD,KAAK,IAAI,IAAI;IAOP,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAoBjF,OAAO,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAa5E,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,SAAS;CAkBlB"}
|