@noopolis/daimon 0.1.1 → 0.2.0
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 +307 -45
- package/dist/contracts/attentionContract.d.ts +117 -0
- package/dist/contracts/attentionContract.d.ts.map +1 -0
- package/dist/contracts/attentionContract.js +14 -0
- package/dist/contracts/canonicalJson.d.ts +3 -0
- package/dist/contracts/canonicalJson.d.ts.map +1 -0
- package/dist/contracts/canonicalJson.js +54 -0
- package/dist/contracts/grokWorkerContract.d.ts +74 -0
- package/dist/contracts/grokWorkerContract.d.ts.map +1 -0
- package/dist/contracts/grokWorkerContract.js +88 -0
- package/dist/contracts/organizationRuntimeContract.d.ts +765 -0
- package/dist/contracts/organizationRuntimeContract.d.ts.map +1 -0
- package/dist/contracts/organizationRuntimeContract.js +121 -0
- package/dist/contracts/runtimeContractManifest.d.ts +1615 -0
- package/dist/contracts/runtimeContractManifest.d.ts.map +1 -0
- package/dist/contracts/runtimeContractManifest.js +181 -0
- package/dist/core/credentialRedaction.d.ts +4 -0
- package/dist/core/credentialRedaction.d.ts.map +1 -0
- package/dist/core/credentialRedaction.js +28 -0
- package/dist/core/siblingBuildFreshness.d.ts +21 -0
- package/dist/core/siblingBuildFreshness.d.ts.map +1 -0
- package/dist/core/siblingBuildFreshness.js +25 -0
- package/dist/core/types.d.ts +10 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/examples/exampleCausalId.d.ts +3 -0
- package/dist/examples/exampleCausalId.d.ts.map +1 -0
- package/dist/examples/exampleCausalId.js +8 -0
- package/dist/examples/jungian-play-org.js +3 -2
- package/dist/examples/jungian-triad-org.js +3 -2
- package/dist/examples/jungianPlayAgent.d.ts +1 -1
- package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.js +1 -1
- package/dist/examples/jungianProfiles.d.ts +1 -1
- package/dist/examples/jungianProfiles.d.ts.map +1 -1
- package/dist/examples/jungianTriadProfiles.d.ts +1 -1
- package/dist/examples/jungianTriadProfiles.d.ts.map +1 -1
- package/dist/examples/pi-agent.js +3 -2
- package/dist/examples/pi-memory-org.js +6 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/mcp/toolServer.d.ts +31 -0
- package/dist/mcp/toolServer.d.ts.map +1 -0
- package/dist/mcp/toolServer.js +145 -0
- package/dist/observability/causalEvents.d.ts +118 -0
- package/dist/observability/causalEvents.d.ts.map +1 -0
- package/dist/observability/causalEvents.js +223 -0
- package/dist/observability/controlCausal.d.ts +117 -0
- package/dist/observability/controlCausal.d.ts.map +1 -0
- package/dist/observability/controlCausal.js +107 -0
- package/dist/observability/emitCausalFixture.d.ts +25 -0
- package/dist/observability/emitCausalFixture.d.ts.map +1 -0
- package/dist/observability/emitCausalFixture.js +98 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +2 -0
- package/dist/observability/orgObserver.d.ts.map +1 -1
- package/dist/observability/orgObserver.js +3 -3
- package/dist/pi/agyHeadlessResult.d.ts +60 -0
- package/dist/pi/agyHeadlessResult.d.ts.map +1 -0
- package/dist/pi/agyHeadlessResult.js +95 -0
- package/dist/pi/cliChildOutput.d.ts +67 -0
- package/dist/pi/cliChildOutput.d.ts.map +1 -0
- package/dist/pi/cliChildOutput.js +269 -0
- package/dist/pi/cliEngineRun.d.ts +16 -0
- package/dist/pi/cliEngineRun.d.ts.map +1 -0
- package/dist/pi/cliEngineRun.js +27 -0
- package/dist/pi/cliEngineSpawn.d.ts +55 -0
- package/dist/pi/cliEngineSpawn.d.ts.map +1 -0
- package/dist/pi/cliEngineSpawn.js +164 -0
- package/dist/pi/cliEnvironment.d.ts +12 -0
- package/dist/pi/cliEnvironment.d.ts.map +1 -0
- package/dist/pi/cliEnvironment.js +32 -0
- package/dist/pi/cliMcpRegistration.d.ts +61 -0
- package/dist/pi/cliMcpRegistration.d.ts.map +1 -0
- package/dist/pi/cliMcpRegistration.js +75 -0
- package/dist/pi/cliProcess.d.ts +7 -0
- package/dist/pi/cliProcess.d.ts.map +1 -0
- package/dist/pi/cliProcess.js +132 -0
- package/dist/pi/cliSession.d.ts +106 -0
- package/dist/pi/cliSession.d.ts.map +1 -0
- package/dist/pi/cliSession.js +379 -0
- package/dist/pi/cliTurnMetering.d.ts +60 -0
- package/dist/pi/cliTurnMetering.d.ts.map +1 -0
- package/dist/pi/cliTurnMetering.js +60 -0
- package/dist/pi/codexFilesystemRules.d.ts +3 -0
- package/dist/pi/codexFilesystemRules.d.ts.map +1 -0
- package/dist/pi/codexFilesystemRules.js +28 -0
- package/dist/pi/codexHeadlessResult.d.ts +68 -0
- package/dist/pi/codexHeadlessResult.d.ts.map +1 -0
- package/dist/pi/codexHeadlessResult.js +122 -0
- package/dist/pi/codexRolloutUsage.d.ts +95 -0
- package/dist/pi/codexRolloutUsage.d.ts.map +1 -0
- package/dist/pi/codexRolloutUsage.js +221 -0
- package/dist/pi/engineWakeLimits.d.ts +39 -0
- package/dist/pi/engineWakeLimits.d.ts.map +1 -0
- package/dist/pi/engineWakeLimits.js +55 -0
- package/dist/pi/grokHeadlessResult.d.ts +36 -0
- package/dist/pi/grokHeadlessResult.d.ts.map +1 -0
- package/dist/pi/grokHeadlessResult.js +118 -0
- package/dist/pi/grokHomeMcpRegistration.d.ts +21 -0
- package/dist/pi/grokHomeMcpRegistration.d.ts.map +1 -0
- package/dist/pi/grokHomeMcpRegistration.js +57 -0
- package/dist/pi/grokSandbox.d.ts +11 -0
- package/dist/pi/grokSandbox.d.ts.map +1 -0
- package/dist/pi/grokSandbox.js +216 -0
- package/dist/pi/grokStreamUsage.d.ts +31 -0
- package/dist/pi/grokStreamUsage.d.ts.map +1 -0
- package/dist/pi/grokStreamUsage.js +51 -0
- package/dist/pi/index.d.ts +5 -0
- package/dist/pi/index.d.ts.map +1 -1
- package/dist/pi/index.js +5 -0
- package/dist/pi/memoryTools.d.ts +55 -1
- package/dist/pi/memoryTools.d.ts.map +1 -1
- package/dist/pi/memoryTools.js +144 -24
- package/dist/pi/modelRegistry.d.ts +13 -0
- package/dist/pi/modelRegistry.d.ts.map +1 -0
- package/dist/pi/modelRegistry.js +19 -0
- package/dist/pi/piAgentHandle.d.ts +46 -0
- package/dist/pi/piAgentHandle.d.ts.map +1 -0
- package/dist/pi/piAgentHandle.js +351 -0
- package/dist/pi/piAgentWakeSupport.d.ts +103 -0
- package/dist/pi/piAgentWakeSupport.d.ts.map +1 -0
- package/dist/pi/piAgentWakeSupport.js +151 -0
- package/dist/pi/piHarness.d.ts +40 -5
- package/dist/pi/piHarness.d.ts.map +1 -1
- package/dist/pi/piHarness.js +151 -256
- package/dist/pi/prompts.d.ts +10 -0
- package/dist/pi/prompts.d.ts.map +1 -0
- package/dist/pi/prompts.js +63 -0
- package/dist/pi/rawTrainingCapture.d.ts +59 -0
- package/dist/pi/rawTrainingCapture.d.ts.map +1 -0
- package/dist/pi/rawTrainingCapture.js +175 -0
- package/dist/pi/turnCausal.d.ts +73 -0
- package/dist/pi/turnCausal.d.ts.map +1 -0
- package/dist/pi/turnCausal.js +75 -0
- package/dist/pi/turnTrace.d.ts +128 -0
- package/dist/pi/turnTrace.d.ts.map +1 -0
- package/dist/pi/turnTrace.js +138 -0
- package/dist/pi/wakeAcceptance.d.ts +49 -0
- package/dist/pi/wakeAcceptance.d.ts.map +1 -0
- package/dist/pi/wakeAcceptance.js +281 -0
- package/dist/pi/wakeAcceptanceFs.d.ts +44 -0
- package/dist/pi/wakeAcceptanceFs.d.ts.map +1 -0
- package/dist/pi/wakeAcceptanceFs.js +318 -0
- package/dist/pi/wakeAcceptanceSchema.d.ts +75 -0
- package/dist/pi/wakeAcceptanceSchema.d.ts.map +1 -0
- package/dist/pi/wakeAcceptanceSchema.js +265 -0
- package/dist/pi/wakeModes.d.ts +9 -0
- package/dist/pi/wakeModes.d.ts.map +1 -0
- package/dist/pi/wakeModes.js +19 -0
- package/dist/pi/worldNudge.d.ts +22 -0
- package/dist/pi/worldNudge.d.ts.map +1 -0
- package/dist/pi/worldNudge.js +62 -0
- package/dist/pi/worldToolProtocol.d.ts +13 -0
- package/dist/pi/worldToolProtocol.d.ts.map +1 -0
- package/dist/pi/worldToolProtocol.js +49 -0
- package/dist/pi/worldTools.d.ts +32 -0
- package/dist/pi/worldTools.d.ts.map +1 -0
- package/dist/pi/worldTools.js +373 -0
- package/dist/pi/worldTrajectory.d.ts +52 -0
- package/dist/pi/worldTrajectory.d.ts.map +1 -0
- package/dist/pi/worldTrajectory.js +131 -0
- package/dist/runtime/agySubscriptionBootstrap.d.ts +16 -0
- package/dist/runtime/agySubscriptionBootstrap.d.ts.map +1 -0
- package/dist/runtime/agySubscriptionBootstrap.js +80 -0
- package/dist/runtime/agySubscriptionRealm.d.ts +38 -0
- package/dist/runtime/agySubscriptionRealm.d.ts.map +1 -0
- package/dist/runtime/agySubscriptionRealm.js +242 -0
- package/dist/runtime/attention.d.ts +21 -0
- package/dist/runtime/attention.d.ts.map +1 -0
- package/dist/runtime/attention.js +36 -0
- package/dist/runtime/attentionDispatcher.d.ts +66 -0
- package/dist/runtime/attentionDispatcher.d.ts.map +1 -0
- package/dist/runtime/attentionDispatcher.js +271 -0
- package/dist/runtime/cli.d.ts +7 -0
- package/dist/runtime/cli.d.ts.map +1 -0
- package/dist/runtime/cli.js +251 -0
- package/dist/runtime/codexSandboxProjection.d.ts +17 -0
- package/dist/runtime/codexSandboxProjection.d.ts.map +1 -0
- package/dist/runtime/codexSandboxProjection.js +40 -0
- package/dist/runtime/contract-manifest.json +1 -0
- package/dist/runtime/contract-manifest.sha256 +1 -0
- package/dist/runtime/contractManifest.d.ts +8 -0
- package/dist/runtime/contractManifest.d.ts.map +1 -0
- package/dist/runtime/contractManifest.js +4 -0
- package/dist/runtime/engineBrokerCapabilities.d.ts +30 -0
- package/dist/runtime/engineBrokerCapabilities.d.ts.map +1 -0
- package/dist/runtime/engineBrokerCapabilities.js +59 -0
- package/dist/runtime/engineBrokerControlClient.d.ts +44 -0
- package/dist/runtime/engineBrokerControlClient.d.ts.map +1 -0
- package/dist/runtime/engineBrokerControlClient.js +132 -0
- package/dist/runtime/engineBrokerCredentialJournal.d.ts +10 -0
- package/dist/runtime/engineBrokerCredentialJournal.d.ts.map +1 -0
- package/dist/runtime/engineBrokerCredentialJournal.js +17 -0
- package/dist/runtime/engineBrokerInferenceProtocol.d.ts +52 -0
- package/dist/runtime/engineBrokerInferenceProtocol.d.ts.map +1 -0
- package/dist/runtime/engineBrokerInferenceProtocol.js +65 -0
- package/dist/runtime/engineBrokerMcpCallLog.d.ts +160 -0
- package/dist/runtime/engineBrokerMcpCallLog.d.ts.map +1 -0
- package/dist/runtime/engineBrokerMcpCallLog.js +219 -0
- package/dist/runtime/engineBrokerMcpFacade.d.ts +56 -0
- package/dist/runtime/engineBrokerMcpFacade.d.ts.map +1 -0
- package/dist/runtime/engineBrokerMcpFacade.js +343 -0
- package/dist/runtime/engineBrokerNativeClient.d.ts +52 -0
- package/dist/runtime/engineBrokerNativeClient.d.ts.map +1 -0
- package/dist/runtime/engineBrokerNativeClient.js +151 -0
- package/dist/runtime/engineBrokerProtocol.d.ts +135 -0
- package/dist/runtime/engineBrokerProtocol.d.ts.map +1 -0
- package/dist/runtime/engineBrokerProtocol.js +274 -0
- package/dist/runtime/engineBrokerSealLedger.d.ts +70 -0
- package/dist/runtime/engineBrokerSealLedger.d.ts.map +1 -0
- package/dist/runtime/engineBrokerSealLedger.js +123 -0
- package/dist/runtime/engineBrokerService.d.ts +37 -0
- package/dist/runtime/engineBrokerService.d.ts.map +1 -0
- package/dist/runtime/engineBrokerService.js +138 -0
- package/dist/runtime/engineBrokerServiceCli.d.ts +5 -0
- package/dist/runtime/engineBrokerServiceCli.d.ts.map +1 -0
- package/dist/runtime/engineBrokerServiceCli.js +32 -0
- package/dist/runtime/engineBrokerServiceConfig.d.ts +50 -0
- package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -0
- package/dist/runtime/engineBrokerServiceConfig.js +92 -0
- package/dist/runtime/engineBrokerSingleflight.d.ts +13 -0
- package/dist/runtime/engineBrokerSingleflight.d.ts.map +1 -0
- package/dist/runtime/engineBrokerSingleflight.js +24 -0
- package/dist/runtime/engineBrokerTurnAccounting.d.ts +76 -0
- package/dist/runtime/engineBrokerTurnAccounting.d.ts.map +1 -0
- package/dist/runtime/engineBrokerTurnAccounting.js +108 -0
- package/dist/runtime/engineBrokerTurnRegistry.d.ts +57 -0
- package/dist/runtime/engineBrokerTurnRegistry.d.ts.map +1 -0
- package/dist/runtime/engineBrokerTurnRegistry.js +158 -0
- package/dist/runtime/engineDispatcher.d.ts +36 -0
- package/dist/runtime/engineDispatcher.d.ts.map +1 -0
- package/dist/runtime/engineDispatcher.js +216 -0
- package/dist/runtime/engineReadiness.d.ts +23 -0
- package/dist/runtime/engineReadiness.d.ts.map +1 -0
- package/dist/runtime/engineReadiness.js +222 -0
- package/dist/runtime/grokAuthenticationError.d.ts +8 -0
- package/dist/runtime/grokAuthenticationError.d.ts.map +1 -0
- package/dist/runtime/grokAuthenticationError.js +20 -0
- package/dist/runtime/grokBrokerCredentialAuthority.d.ts +22 -0
- package/dist/runtime/grokBrokerCredentialAuthority.d.ts.map +1 -0
- package/dist/runtime/grokBrokerCredentialAuthority.js +124 -0
- package/dist/runtime/grokBrokerCredentialReader.d.ts +5 -0
- package/dist/runtime/grokBrokerCredentialReader.d.ts.map +1 -0
- package/dist/runtime/grokBrokerCredentialReader.js +37 -0
- package/dist/runtime/grokBrokerModelPolicy.d.ts +23 -0
- package/dist/runtime/grokBrokerModelPolicy.d.ts.map +1 -0
- package/dist/runtime/grokBrokerModelPolicy.js +28 -0
- package/dist/runtime/grokBrokerProjection.d.ts +106 -0
- package/dist/runtime/grokBrokerProjection.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProjection.js +94 -0
- package/dist/runtime/grokBrokerProxy.d.ts +55 -0
- package/dist/runtime/grokBrokerProxy.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProxy.js +262 -0
- package/dist/runtime/grokBrokerProxyRequest.d.ts +30 -0
- package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProxyRequest.js +74 -0
- package/dist/runtime/grokBrokerRealmLease.d.ts +4 -0
- package/dist/runtime/grokBrokerRealmLease.d.ts.map +1 -0
- package/dist/runtime/grokBrokerRealmLease.js +33 -0
- package/dist/runtime/grokBrokerRefresh.d.ts +2 -0
- package/dist/runtime/grokBrokerRefresh.d.ts.map +1 -0
- package/dist/runtime/grokBrokerRefresh.js +15 -0
- package/dist/runtime/grokBrokerTurnMeter.d.ts +128 -0
- package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -0
- package/dist/runtime/grokBrokerTurnMeter.js +251 -0
- package/dist/runtime/grokBrokerWorkerConfig.d.ts +86 -0
- package/dist/runtime/grokBrokerWorkerConfig.d.ts.map +1 -0
- package/dist/runtime/grokBrokerWorkerConfig.js +135 -0
- package/dist/runtime/grokEngineBroker.d.ts +48 -0
- package/dist/runtime/grokEngineBroker.d.ts.map +1 -0
- package/dist/runtime/grokEngineBroker.js +114 -0
- package/dist/runtime/grokEngineBrokerLedger.d.ts +52 -0
- package/dist/runtime/grokEngineBrokerLedger.d.ts.map +1 -0
- package/dist/runtime/grokEngineBrokerLedger.js +114 -0
- package/dist/runtime/grokEngineBrokerMetering.d.ts +51 -0
- package/dist/runtime/grokEngineBrokerMetering.d.ts.map +1 -0
- package/dist/runtime/grokEngineBrokerMetering.js +34 -0
- package/dist/runtime/grokEngineBrokerTurn.d.ts +58 -0
- package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -0
- package/dist/runtime/grokEngineBrokerTurn.js +165 -0
- package/dist/runtime/grokInferenceClientConfig.d.ts +41 -0
- package/dist/runtime/grokInferenceClientConfig.d.ts.map +1 -0
- package/dist/runtime/grokInferenceClientConfig.js +62 -0
- package/dist/runtime/grokInferenceGrants.d.ts +77 -0
- package/dist/runtime/grokInferenceGrants.d.ts.map +1 -0
- package/dist/runtime/grokInferenceGrants.js +116 -0
- package/dist/runtime/grokInferenceProxy.d.ts +29 -0
- package/dist/runtime/grokInferenceProxy.d.ts.map +1 -0
- package/dist/runtime/grokInferenceProxy.js +82 -0
- package/dist/runtime/grokInferenceProxyRequest.d.ts +23 -0
- package/dist/runtime/grokInferenceProxyRequest.d.ts.map +1 -0
- package/dist/runtime/grokInferenceProxyRequest.js +63 -0
- package/dist/runtime/grokSlotPreflightReceipt.d.ts +70 -0
- package/dist/runtime/grokSlotPreflightReceipt.d.ts.map +1 -0
- package/dist/runtime/grokSlotPreflightReceipt.js +101 -0
- package/dist/runtime/grokSubscriptionRealm.d.ts +4 -0
- package/dist/runtime/grokSubscriptionRealm.d.ts.map +1 -0
- package/dist/runtime/grokSubscriptionRealm.js +435 -0
- package/dist/runtime/grokSubscriptionRealmTypes.d.ts +26 -0
- package/dist/runtime/grokSubscriptionRealmTypes.d.ts.map +1 -0
- package/dist/runtime/grokSubscriptionRealmTypes.js +1 -0
- package/dist/runtime/grokWorkerAttestation.d.ts +170 -0
- package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -0
- package/dist/runtime/grokWorkerAttestation.js +266 -0
- package/dist/runtime/grokWorkerDenyPlacement.d.ts +80 -0
- package/dist/runtime/grokWorkerDenyPlacement.d.ts.map +1 -0
- package/dist/runtime/grokWorkerDenyPlacement.js +128 -0
- package/dist/runtime/grokWorkerHomeAttestation.d.ts +30 -0
- package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -0
- package/dist/runtime/grokWorkerHomeAttestation.js +69 -0
- package/dist/runtime/grokWorkerSandboxProfile.d.ts +27 -0
- package/dist/runtime/grokWorkerSandboxProfile.d.ts.map +1 -0
- package/dist/runtime/grokWorkerSandboxProfile.js +44 -0
- package/dist/runtime/grokWorkerTmpAttestation.d.ts +55 -0
- package/dist/runtime/grokWorkerTmpAttestation.d.ts.map +1 -0
- package/dist/runtime/grokWorkerTmpAttestation.js +63 -0
- package/dist/runtime/index.d.ts +26 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +23 -0
- package/dist/runtime/inferenceUsageLedger.d.ts +38 -0
- package/dist/runtime/inferenceUsageLedger.d.ts.map +1 -0
- package/dist/runtime/inferenceUsageLedger.js +53 -0
- package/dist/runtime/mcpToolResult.d.ts +127 -0
- package/dist/runtime/mcpToolResult.d.ts.map +1 -0
- package/dist/runtime/mcpToolResult.js +214 -0
- package/dist/runtime/moltnetMachineRead.d.ts +97 -0
- package/dist/runtime/moltnetMachineRead.d.ts.map +1 -0
- package/dist/runtime/moltnetMachineRead.js +183 -0
- package/dist/runtime/native/daimon-engine-broker +0 -0
- package/dist/runtime/organizationRuntime.d.ts +165 -0
- package/dist/runtime/organizationRuntime.d.ts.map +1 -0
- package/dist/runtime/organizationRuntime.js +3 -0
- package/dist/runtime/organizationRuntimeControl.d.ts +42 -0
- package/dist/runtime/organizationRuntimeControl.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeControl.js +235 -0
- package/dist/runtime/organizationRuntimeHost.d.ts +19 -0
- package/dist/runtime/organizationRuntimeHost.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeHost.js +359 -0
- package/dist/runtime/organizationRuntimeParsing.d.ts +6 -0
- package/dist/runtime/organizationRuntimeParsing.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeParsing.js +370 -0
- package/dist/runtime/organizationRuntimeReadiness.d.ts +12 -0
- package/dist/runtime/organizationRuntimeReadiness.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeReadiness.js +59 -0
- package/dist/runtime/physicalReadiness.d.ts +47 -0
- package/dist/runtime/physicalReadiness.d.ts.map +1 -0
- package/dist/runtime/physicalReadiness.js +142 -0
- package/dist/runtime/portableCredentialAuth.d.ts +6 -0
- package/dist/runtime/portableCredentialAuth.d.ts.map +1 -0
- package/dist/runtime/portableCredentialAuth.js +70 -0
- package/dist/runtime/portableCredentialMaterial.d.ts +8 -0
- package/dist/runtime/portableCredentialMaterial.d.ts.map +1 -0
- package/dist/runtime/portableCredentialMaterial.js +169 -0
- package/dist/runtime/productionAgentTools.d.ts +5 -0
- package/dist/runtime/productionAgentTools.d.ts.map +1 -0
- package/dist/runtime/productionAgentTools.js +328 -0
- package/dist/runtime/runtimeHomeLayout.d.ts +48 -0
- package/dist/runtime/runtimeHomeLayout.d.ts.map +1 -0
- package/dist/runtime/runtimeHomeLayout.js +84 -0
- package/dist/runtime/schedule.d.ts +40 -0
- package/dist/runtime/schedule.d.ts.map +1 -0
- package/dist/runtime/schedule.js +371 -0
- package/dist/runtime/storeCoordination.d.ts +23 -0
- package/dist/runtime/storeCoordination.d.ts.map +1 -0
- package/dist/runtime/storeCoordination.js +94 -0
- package/dist/runtime/toolResultSpill.d.ts +152 -0
- package/dist/runtime/toolResultSpill.d.ts.map +1 -0
- package/dist/runtime/toolResultSpill.js +351 -0
- package/dist/runtime/turnRequestLedger.d.ts +160 -0
- package/dist/runtime/turnRequestLedger.d.ts.map +1 -0
- package/dist/runtime/turnRequestLedger.js +249 -0
- package/dist/runtime/turnUsageLedger.d.ts +143 -0
- package/dist/runtime/turnUsageLedger.d.ts.map +1 -0
- package/dist/runtime/turnUsageLedger.js +203 -0
- package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts +17 -0
- package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceProcessIdentity.js +50 -0
- package/dist/runtime/wakeAcceptanceReconciliation.d.ts +211 -0
- package/dist/runtime/wakeAcceptanceReconciliation.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceReconciliation.js +289 -0
- package/dist/runtime/wakeAcceptanceRecord.d.ts +23 -0
- package/dist/runtime/wakeAcceptanceRecord.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceRecord.js +56 -0
- package/dist/runtime/wakeAcceptanceRetention.d.ts +12 -0
- package/dist/runtime/wakeAcceptanceRetention.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceRetention.js +12 -0
- package/dist/runtime/wakeAcceptanceStore.d.ts +113 -0
- package/dist/runtime/wakeAcceptanceStore.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceStore.js +558 -0
- package/dist/runtime/wakeAcceptanceTypes.d.ts +179 -0
- package/dist/runtime/wakeAcceptanceTypes.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceTypes.js +95 -0
- package/dist/runtime/wakeFuse.d.ts +69 -0
- package/dist/runtime/wakeFuse.d.ts.map +1 -0
- package/dist/runtime/wakeFuse.js +367 -0
- package/package.json +25 -17
- package/dist/examples/mixed-engine-org.d.ts +0 -2
- package/dist/examples/mixed-engine-org.d.ts.map +0 -1
- package/dist/examples/mixed-engine-org.js +0 -351
- package/dist/examples/mixedEngineCli.d.ts +0 -15
- package/dist/examples/mixedEngineCli.d.ts.map +0 -1
- package/dist/examples/mixedEngineCli.js +0 -174
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { mkdir, open } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Mode for every directory Daimon creates inside an agent's runtime home.
|
|
6
|
+
*
|
|
7
|
+
* A brokered Grok agent's runtime home is `0710`
|
|
8
|
+
* (`GROK_ENGINE_BROKER.worker.home.organizationRuntimeHome`) so its sandboxed
|
|
9
|
+
* worker can traverse into the setgid `tool-output/` spill directory. Traverse
|
|
10
|
+
* is all it may have: anything Daimon creates in that home — telemetry traces
|
|
11
|
+
* (prompts, replies, world trajectories), tool state, receipts, the engine's
|
|
12
|
+
* XDG directories and the private `.tmp` — stays `0700`, so a default
|
|
13
|
+
* `mkdir` (0755 under the usual umask) never turns a traversable home into a
|
|
14
|
+
* readable one.
|
|
15
|
+
*/
|
|
16
|
+
export const RUNTIME_HOME_SUBDIRECTORY_MODE = 0o700;
|
|
17
|
+
/**
|
|
18
|
+
* The home itself, created if it is absent and otherwise left exactly as it is.
|
|
19
|
+
*
|
|
20
|
+
* Create-only is the whole contract here. A brokered Grok agent's home is
|
|
21
|
+
* deliberately `0710` and an organization's may be `0700`; which of the two is
|
|
22
|
+
* correct is `physicalReadiness.ts`'s judgement, made against the agent's
|
|
23
|
+
* declared engine, and a layout helper that "corrected" a traversable home to
|
|
24
|
+
* `0700` would break the worker's only route to its own spills.
|
|
25
|
+
*/
|
|
26
|
+
export const ensureRuntimeHome = async (runtimeHomePath) => {
|
|
27
|
+
await mkdir(runtimeHomePath, { recursive: true, mode: RUNTIME_HOME_SUBDIRECTORY_MODE });
|
|
28
|
+
return runtimeHomePath;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* One directory Daimon owns *below* a runtime home, private on every install.
|
|
32
|
+
*
|
|
33
|
+
* `mkdir(..., { mode })` decides nothing for a directory that already exists,
|
|
34
|
+
* and that is the common case rather than the exotic one: a `telemetry/` left
|
|
35
|
+
* at `0755` by a pre-branch Daimon, or pre-created by a deployment, stayed
|
|
36
|
+
* `0755` forever. Under a Grok agent's traversable `0710` home that is the
|
|
37
|
+
* worker reading its own agent's prompts, replies and causal history — the
|
|
38
|
+
* home is traverse-only precisely so that nothing but `tool-output/` is
|
|
39
|
+
* readable. `assertRuntimeDirectory` checks the home, and nothing checked what
|
|
40
|
+
* Daimon created inside it.
|
|
41
|
+
*
|
|
42
|
+
* So every level below the home is asserted and corrected on the way down,
|
|
43
|
+
* through a handle rather than a path: `O_DIRECTORY|O_NOFOLLOW` refuses a
|
|
44
|
+
* symlink planted where a directory belongs, and the `fchmod` that follows
|
|
45
|
+
* lands on the directory that was stat'd. A directory owned by anyone but the
|
|
46
|
+
* runtime user is **refused**, never widened and never silently accepted — the
|
|
47
|
+
* runtime cannot make someone else's directory private, and proceeding would
|
|
48
|
+
* write an agent's telemetry into it anyway.
|
|
49
|
+
*
|
|
50
|
+
* `owner` is a seam so both refusals are testable unprivileged, exactly as
|
|
51
|
+
* `physicalReadiness.ts`'s `RuntimeIdentity` is.
|
|
52
|
+
*/
|
|
53
|
+
export async function ensureRuntimeHomeDirectory(runtimeHomePath, relative, owner = process.getuid?.() ?? -1) {
|
|
54
|
+
const segments = relative.split("/").filter((segment) => segment.length > 0);
|
|
55
|
+
if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) {
|
|
56
|
+
throw new Error(`runtime home subdirectory must name a path below the home: ${relative}`);
|
|
57
|
+
}
|
|
58
|
+
let current = await ensureRuntimeHome(runtimeHomePath);
|
|
59
|
+
for (const segment of segments) {
|
|
60
|
+
current = path.join(current, segment);
|
|
61
|
+
await mkdir(current, { mode: RUNTIME_HOME_SUBDIRECTORY_MODE }).catch((error) => {
|
|
62
|
+
if (error.code !== "EEXIST")
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
65
|
+
await assertPrivateDirectory(current, owner);
|
|
66
|
+
}
|
|
67
|
+
return current;
|
|
68
|
+
}
|
|
69
|
+
const flag = (name) => constants[name] ?? 0;
|
|
70
|
+
async function assertPrivateDirectory(directory, owner) {
|
|
71
|
+
const handle = await open(directory, constants.O_RDONLY | flag("O_DIRECTORY") | flag("O_NOFOLLOW"));
|
|
72
|
+
try {
|
|
73
|
+
const entry = await handle.stat();
|
|
74
|
+
if (!entry.isDirectory())
|
|
75
|
+
throw new Error(`runtime home path is not a directory: ${directory}`);
|
|
76
|
+
if (entry.uid !== owner)
|
|
77
|
+
throw new Error(`runtime home subdirectory must be owned by the runtime user: ${directory}`);
|
|
78
|
+
if ((entry.mode & 0o7777) !== RUNTIME_HOME_SUBDIRECTORY_MODE)
|
|
79
|
+
await handle.chmod(RUNTIME_HOME_SUBDIRECTORY_MODE);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await handle.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { OrganizationRuntimeAgentConfig, OrganizationRuntimeSchedule } from "./organizationRuntime.js";
|
|
2
|
+
export declare const MAX_TIMER_DELAY_MS = 2147483647;
|
|
3
|
+
export type ScheduledOccurrence = Readonly<{
|
|
4
|
+
agentId: string;
|
|
5
|
+
deliveryId: string;
|
|
6
|
+
occurredAt: string;
|
|
7
|
+
prompt: string;
|
|
8
|
+
}>;
|
|
9
|
+
type ActiveSchedule = Exclude<OrganizationRuntimeSchedule, {
|
|
10
|
+
kind: "disabled";
|
|
11
|
+
}>;
|
|
12
|
+
type Timer = ReturnType<typeof setTimeout>;
|
|
13
|
+
export type ScheduleController = Readonly<{
|
|
14
|
+
start(): Promise<void>;
|
|
15
|
+
drain(agentId?: string): Promise<void>;
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
}>;
|
|
18
|
+
export type ScheduleControllerOptions = Readonly<{
|
|
19
|
+
acceptanceStorePath: string;
|
|
20
|
+
agents: readonly OrganizationRuntimeAgentConfig[];
|
|
21
|
+
/** False means the agent is busy; the durable latest-pending slot remains. */
|
|
22
|
+
accept(occurrence: ScheduledOccurrence): Promise<boolean | void>;
|
|
23
|
+
now?: () => number;
|
|
24
|
+
/** Draws the fractional jitter offset; defaults to `Math.random`. Injectable so tests can assert bounds/independence without real randomness. */
|
|
25
|
+
random?: () => number;
|
|
26
|
+
setTimer?: (callback: () => void, delayMs: number) => Timer;
|
|
27
|
+
clearTimer?: (timer: Timer) => void;
|
|
28
|
+
/** @internal Observable durability stages for focused crash-order tests. */
|
|
29
|
+
onPersistStageForTest?: (stage: "write" | "file-sync" | "rename" | "directory-sync") => void;
|
|
30
|
+
}>;
|
|
31
|
+
/** Schedules create durable pending occurrences; engine execution remains elsewhere. */
|
|
32
|
+
export declare function createScheduleController(options: ScheduleControllerOptions): ScheduleController;
|
|
33
|
+
export declare function occurrenceFor(agentId: string, schedule: ActiveSchedule, at: number): ScheduledOccurrence;
|
|
34
|
+
export declare function isCanonicalScheduleDeliveryId(value: string): boolean;
|
|
35
|
+
export declare function nextOccurrence(agentId: string, schedule: ActiveSchedule, anchor: number | undefined, from: number): number;
|
|
36
|
+
export declare function cronIsPossible(cron: string): boolean;
|
|
37
|
+
/** Independent per-arming offset in `[0, jitter_seconds * 1000]`; absent/zero jitter always yields exactly 0. */
|
|
38
|
+
export declare function jitterOffsetMs(schedule: ActiveSchedule | undefined, random: () => number): number;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../src/runtime/schedule.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,8BAA8B,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAK5G,eAAO,MAAM,kBAAkB,aAAgB,CAAC;AAKhD,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxH,KAAK,cAAc,GAAG,OAAO,CAAC,2BAA2B,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAGjF,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAE3C,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAAC;AACrI,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,MAAM,EAAE,SAAS,8BAA8B,EAAE,CAAC;IAClD,8EAA8E;IAC9E,MAAM,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACjE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,iJAAiJ;IACjJ,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;IAC5D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACpC,4EAA4E;IAC5E,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,gBAAgB,KAAK,IAAI,CAAC;CAC9F,CAAC,CAAC;AAEH,wFAAwF;AACxF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CA8F/F;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,mBAAmB,CAGxG;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKpE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAU1H;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAA8E;AAEnI,iHAAiH;AACjH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,GAAG,MAAM,CAKjG"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, open, readFile, rename, unlink } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const STATE_VERSION = "noopolis.daimon.schedule-state.v1";
|
|
6
|
+
const STATE_FILE = "schedule-state.v1.json";
|
|
7
|
+
const MAX_STATE_BYTES = 256 * 1024;
|
|
8
|
+
export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
9
|
+
const SEARCH_HORIZON_MS = 64 * 366 * 86_400_000;
|
|
10
|
+
const MAX_SEARCH_CHECKS = 1_000_000;
|
|
11
|
+
const CRON_FIELD_BOUNDS = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]];
|
|
12
|
+
/** Schedules create durable pending occurrences; engine execution remains elsewhere. */
|
|
13
|
+
export function createScheduleController(options) {
|
|
14
|
+
const now = options.now ?? Date.now;
|
|
15
|
+
const random = options.random ?? Math.random;
|
|
16
|
+
const setTimer = options.setTimer ?? ((callback, delay) => setTimeout(callback, delay));
|
|
17
|
+
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
18
|
+
const statePath = path.join(options.acceptanceStorePath, STATE_FILE);
|
|
19
|
+
const active = options.agents.filter((agent) => agent.schedule && agent.schedule.kind !== "disabled");
|
|
20
|
+
const byKey = new Map(active.map((agent) => [identity(agent.id, agent.schedule), agent]));
|
|
21
|
+
const timers = new Map();
|
|
22
|
+
let directory;
|
|
23
|
+
let state = { version: STATE_VERSION, schedules: {} };
|
|
24
|
+
let mutations = Promise.resolve();
|
|
25
|
+
let stopped = false;
|
|
26
|
+
const enqueue = (operation) => {
|
|
27
|
+
const result = mutations.catch(() => undefined).then(operation);
|
|
28
|
+
mutations = result.then(() => undefined, () => undefined);
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
const save = async () => {
|
|
32
|
+
if (directory === undefined)
|
|
33
|
+
throw new Error("schedule state directory is unavailable");
|
|
34
|
+
await persist(statePath, state, directory, options.onPersistStageForTest);
|
|
35
|
+
};
|
|
36
|
+
const nextEntry = (agent, anchor, observedNow, latest_pending) => {
|
|
37
|
+
const next_due_ms = nextOccurrence(agent.id, agent.schedule, anchor, observedNow);
|
|
38
|
+
return withFireTarget({ next_due_ms, ...(latest_pending === undefined ? {} : { latest_pending }) }, agent.schedule, random);
|
|
39
|
+
};
|
|
40
|
+
const arm = (key) => {
|
|
41
|
+
const prior = timers.get(key);
|
|
42
|
+
if (prior !== undefined)
|
|
43
|
+
clearTimer(prior);
|
|
44
|
+
if (stopped)
|
|
45
|
+
return;
|
|
46
|
+
const entry = state.schedules[key];
|
|
47
|
+
if (entry === undefined)
|
|
48
|
+
return;
|
|
49
|
+
const target = entry.fire_at_ms ?? entry.next_due_ms;
|
|
50
|
+
const delay = Math.min(MAX_TIMER_DELAY_MS, Math.max(0, target - now()));
|
|
51
|
+
const timer = setTimer(() => { void enqueue(async () => await onTimer(key)); }, delay);
|
|
52
|
+
timer.unref?.();
|
|
53
|
+
timers.set(key, timer);
|
|
54
|
+
};
|
|
55
|
+
const drainKey = async (key) => {
|
|
56
|
+
if (stopped)
|
|
57
|
+
return;
|
|
58
|
+
const pending = state.schedules[key]?.latest_pending;
|
|
59
|
+
if (pending === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const accepted = await options.accept(pending);
|
|
62
|
+
if (accepted === false || stopped)
|
|
63
|
+
return;
|
|
64
|
+
const current = state.schedules[key];
|
|
65
|
+
if (current?.latest_pending?.deliveryId !== pending.deliveryId)
|
|
66
|
+
return;
|
|
67
|
+
state = { ...state, schedules: { ...state.schedules, [key]: withoutPending(current) } };
|
|
68
|
+
await save();
|
|
69
|
+
};
|
|
70
|
+
const onTimer = async (key) => {
|
|
71
|
+
if (stopped)
|
|
72
|
+
return;
|
|
73
|
+
const agent = byKey.get(key);
|
|
74
|
+
const entry = state.schedules[key];
|
|
75
|
+
if (agent === undefined || entry === undefined)
|
|
76
|
+
return;
|
|
77
|
+
const observedNow = now();
|
|
78
|
+
const fireAt = entry.fire_at_ms ?? entry.next_due_ms;
|
|
79
|
+
if (fireAt > observedNow) {
|
|
80
|
+
arm(key);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const elapsed = latestEligibleOccurrence(agent.schedule, entry.next_due_ms, observedNow);
|
|
84
|
+
state = { ...state, schedules: { ...state.schedules, [key]: nextEntry(agent, elapsed, observedNow, occurrenceFor(agent.id, agent.schedule, elapsed)) } };
|
|
85
|
+
await save();
|
|
86
|
+
arm(key);
|
|
87
|
+
await drainKey(key);
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
async start() {
|
|
91
|
+
await enqueue(async () => {
|
|
92
|
+
if (directory !== undefined)
|
|
93
|
+
return;
|
|
94
|
+
directory = await open(options.acceptanceStorePath, constants.O_RDONLY | directoryFlag() | noFollow());
|
|
95
|
+
state = await restore(statePath, byKey);
|
|
96
|
+
const next = { ...state.schedules };
|
|
97
|
+
const observedNow = now();
|
|
98
|
+
for (const [key, agent] of byKey) {
|
|
99
|
+
const prior = next[key];
|
|
100
|
+
if (prior === undefined)
|
|
101
|
+
next[key] = nextEntry(agent, undefined, observedNow);
|
|
102
|
+
else if ((prior.fire_at_ms ?? prior.next_due_ms) <= observedNow) {
|
|
103
|
+
const elapsed = latestEligibleOccurrence(agent.schedule, prior.next_due_ms, observedNow);
|
|
104
|
+
const pending = occurrenceFor(agent.id, agent.schedule, elapsed);
|
|
105
|
+
next[key] = nextEntry(agent, elapsed, observedNow, newer(prior.latest_pending, pending));
|
|
106
|
+
}
|
|
107
|
+
else if (prior.fire_at_ms === undefined) {
|
|
108
|
+
next[key] = withFireTarget(prior, agent.schedule, random);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
state = { version: STATE_VERSION, schedules: next };
|
|
112
|
+
await save();
|
|
113
|
+
for (const key of byKey.keys())
|
|
114
|
+
arm(key);
|
|
115
|
+
for (const key of byKey.keys())
|
|
116
|
+
await drainKey(key);
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
async drain(agentId) { await enqueue(async () => { for (const [key, agent] of byKey)
|
|
120
|
+
if (agentId === undefined || agent.id === agentId)
|
|
121
|
+
await drainKey(key); }); },
|
|
122
|
+
async stop() {
|
|
123
|
+
stopped = true;
|
|
124
|
+
for (const timer of timers.values())
|
|
125
|
+
clearTimer(timer);
|
|
126
|
+
timers.clear();
|
|
127
|
+
await mutations;
|
|
128
|
+
await directory?.close();
|
|
129
|
+
directory = undefined;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function occurrenceFor(agentId, schedule, at) {
|
|
134
|
+
const local = schedule.kind === "cron" ? occurrenceForOffset(at, schedule.timezone) : new Date(at).toISOString();
|
|
135
|
+
return { agentId, occurredAt: new Date(at).toISOString(), prompt: schedule.prompt, deliveryId: `schedule:${identity(agentId, schedule)}:${local}` };
|
|
136
|
+
}
|
|
137
|
+
export function isCanonicalScheduleDeliveryId(value) {
|
|
138
|
+
if (value.length > 256 || !/^schedule:[a-f0-9]{64}:/u.test(value))
|
|
139
|
+
return false;
|
|
140
|
+
const occurrence = value.slice("schedule:".length + 64 + 1);
|
|
141
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(occurrence)
|
|
142
|
+
|| /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}@(?:GMT(?:[+-]\d{2}:\d{2})?|UTC)$/u.test(occurrence);
|
|
143
|
+
}
|
|
144
|
+
export function nextOccurrence(agentId, schedule, anchor, from) {
|
|
145
|
+
if (schedule.kind === "every") {
|
|
146
|
+
const base = anchor ?? from;
|
|
147
|
+
return base > from ? base : base + (Math.floor((from - base) / schedule.interval_ms) + 1) * schedule.interval_ms;
|
|
148
|
+
}
|
|
149
|
+
const fields = parseCron(schedule.cron);
|
|
150
|
+
if (!calendarPossible(fields))
|
|
151
|
+
throw new Error(`schedule ${agentId} is impossible`);
|
|
152
|
+
const found = searchCron(fields, schedule.timezone, from, from + SEARCH_HORIZON_MS, 1);
|
|
153
|
+
if (found !== undefined)
|
|
154
|
+
return found;
|
|
155
|
+
throw new Error(`schedule ${agentId} has no occurrence within the deterministic search bound`);
|
|
156
|
+
}
|
|
157
|
+
export function cronIsPossible(cron) { try {
|
|
158
|
+
return calendarPossible(parseCron(cron));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false;
|
|
162
|
+
} }
|
|
163
|
+
/** Independent per-arming offset in `[0, jitter_seconds * 1000]`; absent/zero jitter always yields exactly 0. */
|
|
164
|
+
export function jitterOffsetMs(schedule, random) {
|
|
165
|
+
const jitterSeconds = schedule?.jitter_seconds ?? 0;
|
|
166
|
+
if (jitterSeconds <= 0)
|
|
167
|
+
return 0;
|
|
168
|
+
const bound = jitterSeconds * 1_000;
|
|
169
|
+
return Math.min(bound, Math.max(0, Math.floor(random() * (bound + 1))));
|
|
170
|
+
}
|
|
171
|
+
function withFireTarget(entry, schedule, random) {
|
|
172
|
+
const offset = jitterOffsetMs(schedule, random);
|
|
173
|
+
return offset === 0 ? entry : { ...entry, fire_at_ms: entry.next_due_ms + offset };
|
|
174
|
+
}
|
|
175
|
+
function withoutPending(entry) {
|
|
176
|
+
return { next_due_ms: entry.next_due_ms, ...(entry.fire_at_ms === undefined ? {} : { fire_at_ms: entry.fire_at_ms }) };
|
|
177
|
+
}
|
|
178
|
+
function latestEligibleOccurrence(schedule, due, at) {
|
|
179
|
+
if (schedule.kind === "every")
|
|
180
|
+
return due + Math.floor((at - due) / schedule.interval_ms) * schedule.interval_ms;
|
|
181
|
+
const result = searchCron(parseCron(schedule.cron), schedule.timezone, Math.max(due, at - SEARCH_HORIZON_MS), at, -1);
|
|
182
|
+
if (result === undefined)
|
|
183
|
+
throw new Error("schedule state has no eligible occurrence within the deterministic search bound");
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
function parseCron(cron) {
|
|
187
|
+
const raw = cron.trim().split(/\s+/u);
|
|
188
|
+
if (raw.length !== 5)
|
|
189
|
+
throw new Error("cron is invalid");
|
|
190
|
+
return Object.assign(raw.map((field, index) => parseField(field, CRON_FIELD_BOUNDS[index])), {
|
|
191
|
+
dayOfMonthWildcard: raw[2] === "*", dayOfWeekWildcard: raw[4] === "*"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function parseField(value, [minimum, maximum]) {
|
|
195
|
+
const result = new Set();
|
|
196
|
+
for (const part of value.split(",")) {
|
|
197
|
+
const [range, rawStep] = part.split("/");
|
|
198
|
+
const step = Number(rawStep ?? 1);
|
|
199
|
+
const bounds = range === "*" ? [minimum, maximum] : range.split("-").map(Number);
|
|
200
|
+
const first = bounds[0];
|
|
201
|
+
const last = bounds[1] ?? first;
|
|
202
|
+
if (!Number.isSafeInteger(step) || step < 1 || !Number.isInteger(first) || !Number.isInteger(last) || first < minimum || last > maximum || first > last)
|
|
203
|
+
throw new Error("cron is invalid");
|
|
204
|
+
for (let current = first; current <= last; current += step)
|
|
205
|
+
result.add(current === 7 && maximum === 7 ? 0 : current);
|
|
206
|
+
}
|
|
207
|
+
return [...result].sort((left, right) => left - right);
|
|
208
|
+
}
|
|
209
|
+
function calendarPossible(fields) {
|
|
210
|
+
for (let year = 2000; year < 2400; year += 1)
|
|
211
|
+
for (const month of fields[3]) {
|
|
212
|
+
const days = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
213
|
+
for (let day = 1; day <= days; day += 1)
|
|
214
|
+
if (dateMatches(fields, { year, month, day, hour: 0, minute: 0, weekday: new Date(Date.UTC(year, month - 1, day)).getUTCDay() }))
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
function searchCron(fields, zone, start, end, direction) {
|
|
220
|
+
let checks = 0;
|
|
221
|
+
const firstDay = Math.floor((direction === 1 ? start : end) / 86_400_000) * 86_400_000;
|
|
222
|
+
const lastDay = Math.floor((direction === 1 ? end : start) / 86_400_000) * 86_400_000;
|
|
223
|
+
for (let day = firstDay; direction === 1 ? day <= lastDay : day >= lastDay; day += direction * 86_400_000) {
|
|
224
|
+
const boundary = [parts(day, zone), parts(day + 86_340_000, zone)];
|
|
225
|
+
checks += 2;
|
|
226
|
+
if (!boundary.some((value) => dateMatches(fields, value)))
|
|
227
|
+
continue;
|
|
228
|
+
const nextMinute = Math.floor(start / 60_000) * 60_000 + 60_000;
|
|
229
|
+
const low = Math.max(direction === 1 ? nextMinute : start, day);
|
|
230
|
+
const high = Math.min(end, day + 86_340_000);
|
|
231
|
+
let candidate = direction === 1 ? Math.ceil(low / 60_000) * 60_000 : Math.floor(high / 60_000) * 60_000;
|
|
232
|
+
for (; direction === 1 ? candidate <= high : candidate >= low; candidate += direction * 60_000) {
|
|
233
|
+
if (++checks > MAX_SEARCH_CHECKS)
|
|
234
|
+
throw new Error("cron search exceeded its deterministic work bound");
|
|
235
|
+
if (matches(fields, parts(candidate, zone)))
|
|
236
|
+
return candidate;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
function dateMatches(fields, value) {
|
|
242
|
+
if (!fields[3].includes(value.month))
|
|
243
|
+
return false;
|
|
244
|
+
const dom = fields[2].includes(value.day);
|
|
245
|
+
const dow = fields[4].includes(value.weekday);
|
|
246
|
+
return fields.dayOfMonthWildcard ? dow : fields.dayOfWeekWildcard ? dom : dom || dow;
|
|
247
|
+
}
|
|
248
|
+
function matches(fields, value) { return fields[0].includes(value.minute) && fields[1].includes(value.hour) && dateMatches(fields, value); }
|
|
249
|
+
const formatters = new Map();
|
|
250
|
+
function formatter(zone) {
|
|
251
|
+
let value = formatters.get(zone);
|
|
252
|
+
if (value === undefined) {
|
|
253
|
+
value = new Intl.DateTimeFormat("en-US", { timeZone: zone, hourCycle: "h23", weekday: "short", year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric" });
|
|
254
|
+
formatters.set(zone, value);
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function parts(at, zone) {
|
|
259
|
+
const values = formatter(zone).formatToParts(at);
|
|
260
|
+
const get = (name) => values.find((part) => part.type === name)?.value ?? "0";
|
|
261
|
+
return { year: Number(get("year")), month: Number(get("month")), day: Number(get("day")), hour: Number(get("hour")), minute: Number(get("minute")), weekday: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(get("weekday")) };
|
|
262
|
+
}
|
|
263
|
+
function occurrenceForOffset(at, zone) {
|
|
264
|
+
const value = parts(at, zone);
|
|
265
|
+
const local = `${value.year}-${String(value.month).padStart(2, "0")}-${String(value.day).padStart(2, "0")}T${String(value.hour).padStart(2, "0")}:${String(value.minute).padStart(2, "0")}`;
|
|
266
|
+
const key = `${zone}\u0000offset`;
|
|
267
|
+
let offset = formatters.get(key);
|
|
268
|
+
if (offset === undefined) {
|
|
269
|
+
offset = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "longOffset" });
|
|
270
|
+
formatters.set(key, offset);
|
|
271
|
+
}
|
|
272
|
+
return `${local}@${offset.formatToParts(at).find((part) => part.type === "timeZoneName")?.value ?? "UTC"}`;
|
|
273
|
+
}
|
|
274
|
+
function identity(agentId, schedule) { return createHash("sha256").update(JSON.stringify({ agentId, schedule })).digest("hex"); }
|
|
275
|
+
function newer(left, right) { return left !== undefined && Date.parse(left.occurredAt) > Date.parse(right.occurredAt) ? left : right; }
|
|
276
|
+
async function restore(file, allowed) {
|
|
277
|
+
let bytes;
|
|
278
|
+
try {
|
|
279
|
+
const entry = await lstat(file);
|
|
280
|
+
if (!entry.isFile() || entry.isSymbolicLink() || (entry.mode & 0o777) !== 0o600 || entry.size > MAX_STATE_BYTES)
|
|
281
|
+
throw new Error("schedule state is unsafe");
|
|
282
|
+
bytes = await readFile(file);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (error.code === "ENOENT")
|
|
286
|
+
return { version: STATE_VERSION, schedules: {} };
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
if (bytes.length > MAX_STATE_BYTES)
|
|
290
|
+
throw new Error("schedule state exceeds its bound");
|
|
291
|
+
const value = JSON.parse(bytes.toString("utf8"));
|
|
292
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
293
|
+
throw new Error("schedule state is invalid");
|
|
294
|
+
const root = value;
|
|
295
|
+
if (Object.keys(root).sort().join() !== "schedules,version" || root.version !== STATE_VERSION || !root.schedules || typeof root.schedules !== "object" || Array.isArray(root.schedules))
|
|
296
|
+
throw new Error("schedule state is invalid");
|
|
297
|
+
const schedules = {};
|
|
298
|
+
for (const [key, raw] of Object.entries(root.schedules)) {
|
|
299
|
+
if (!/^[a-f0-9]{64}$/u.test(key))
|
|
300
|
+
throw new Error("schedule state is invalid");
|
|
301
|
+
if (!allowed.has(key))
|
|
302
|
+
continue;
|
|
303
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
304
|
+
throw new Error("schedule state is invalid");
|
|
305
|
+
const entry = raw;
|
|
306
|
+
const keys = Object.keys(entry).sort().join();
|
|
307
|
+
if (!["fire_at_ms,next_due_ms", "fire_at_ms,latest_pending,next_due_ms", "latest_pending,next_due_ms", "next_due_ms"].includes(keys))
|
|
308
|
+
throw new Error("schedule state is invalid");
|
|
309
|
+
if (!Number.isSafeInteger(entry.next_due_ms) || entry.next_due_ms < 0)
|
|
310
|
+
throw new Error("schedule state is invalid");
|
|
311
|
+
const nextDue = entry.next_due_ms;
|
|
312
|
+
const fireAt = entry.fire_at_ms;
|
|
313
|
+
if (fireAt !== undefined) {
|
|
314
|
+
const maximumFireAt = nextDue + jitterOffsetMs(allowed.get(key).schedule, () => 0.999_999_999);
|
|
315
|
+
if (!Number.isSafeInteger(fireAt) || fireAt < nextDue || fireAt > maximumFireAt)
|
|
316
|
+
throw new Error("schedule state is invalid");
|
|
317
|
+
}
|
|
318
|
+
const pending = entry.latest_pending === undefined ? undefined : parseOccurrence(entry.latest_pending);
|
|
319
|
+
if (pending !== undefined)
|
|
320
|
+
assertRestoredOccurrence(key, allowed.get(key), pending, nextDue);
|
|
321
|
+
schedules[key] = { next_due_ms: nextDue, ...(fireAt === undefined ? {} : { fire_at_ms: fireAt }), ...(pending === undefined ? {} : { latest_pending: pending }) };
|
|
322
|
+
}
|
|
323
|
+
return { version: STATE_VERSION, schedules };
|
|
324
|
+
}
|
|
325
|
+
function assertRestoredOccurrence(key, agent, pending, nextDue) {
|
|
326
|
+
const occurred = Date.parse(pending.occurredAt);
|
|
327
|
+
const expected = occurrenceFor(agent.id, agent.schedule, occurred);
|
|
328
|
+
if (pending.agentId !== agent.id || pending.prompt !== agent.schedule.prompt ||
|
|
329
|
+
pending.deliveryId !== expected.deliveryId || !pending.deliveryId.startsWith(`schedule:${key}:`) ||
|
|
330
|
+
nextDue !== nextOccurrence(agent.id, agent.schedule, occurred, occurred)) {
|
|
331
|
+
throw new Error("schedule state is invalid");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function parseOccurrence(value) {
|
|
335
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
336
|
+
throw new Error("schedule state is invalid");
|
|
337
|
+
const item = value;
|
|
338
|
+
if (Object.keys(item).sort().join() !== "agentId,deliveryId,occurredAt,prompt" || Object.values(item).some((entry) => typeof entry !== "string" || Buffer.byteLength(entry, "utf8") > 16_384))
|
|
339
|
+
throw new Error("schedule state is invalid");
|
|
340
|
+
const occurredAt = item.occurredAt;
|
|
341
|
+
if (Number.isNaN(Date.parse(occurredAt)) || new Date(occurredAt).toISOString() !== occurredAt || !/^schedule:[a-f0-9]{64}:/u.test(item.deliveryId))
|
|
342
|
+
throw new Error("schedule state is invalid");
|
|
343
|
+
return item;
|
|
344
|
+
}
|
|
345
|
+
async function persist(file, value, directory, observe) {
|
|
346
|
+
const bytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
347
|
+
if (bytes.length > MAX_STATE_BYTES)
|
|
348
|
+
throw new Error("schedule state exceeds its bound");
|
|
349
|
+
const temporary = path.join(path.dirname(file), `.schedule-state-${randomUUID()}`);
|
|
350
|
+
const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow(), 0o600);
|
|
351
|
+
try {
|
|
352
|
+
await handle.writeFile(bytes);
|
|
353
|
+
observe?.("write");
|
|
354
|
+
await handle.sync();
|
|
355
|
+
observe?.("file-sync");
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
await handle.close();
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
await rename(temporary, file);
|
|
362
|
+
observe?.("rename");
|
|
363
|
+
await directory.sync();
|
|
364
|
+
observe?.("directory-sync");
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
await unlink(temporary).catch(() => undefined);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function noFollow() { return constants.O_NOFOLLOW ?? 0; }
|
|
371
|
+
function directoryFlag() { return constants.O_DIRECTORY ?? 0; }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
export type StoreProcessIdentity = Readonly<{
|
|
3
|
+
pid: number;
|
|
4
|
+
process_start: string;
|
|
5
|
+
boot_id: string;
|
|
6
|
+
pid_namespace_dev: number;
|
|
7
|
+
pid_namespace_ino: number;
|
|
8
|
+
}>;
|
|
9
|
+
export type StoreHostRegistration = StoreProcessIdentity & Readonly<{
|
|
10
|
+
owner_id: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type StoreHostRegistrationIdentity = StoreHostRegistration & Readonly<{
|
|
13
|
+
dev: number;
|
|
14
|
+
ino: number;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function acquireHostRegistration(root: string, directory: Awaited<ReturnType<typeof open>>, identity: StoreProcessIdentity): Promise<StoreHostRegistration>;
|
|
17
|
+
export declare function releaseHostRegistration(root: string, directory: Awaited<ReturnType<typeof open>>, registration: StoreHostRegistration): Promise<void>;
|
|
18
|
+
/** Admin callers hold their durable offline lease before calling this. */
|
|
19
|
+
export declare function listHostRegistrations(root: string): Promise<readonly StoreHostRegistrationIdentity[]>;
|
|
20
|
+
/** Returns undefined when any registration is live, unknown, or changed. */
|
|
21
|
+
export declare function clearDeadHostRegistrations(root: string, directory: Awaited<ReturnType<typeof open>>, identity: StoreProcessIdentity, liveness: (owner: StoreHostRegistration) => Promise<boolean>, authorizedCrossNamespace: ReadonlySet<string>): Promise<readonly string[] | undefined>;
|
|
22
|
+
export declare function hostRegistrationDigest(value: StoreHostRegistrationIdentity): string;
|
|
23
|
+
//# sourceMappingURL=storeCoordination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storeCoordination.d.ts","sourceRoot":"","sources":["../../src/runtime/storeCoordination.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,IAAI,EAA6B,MAAM,kBAAkB,CAAC;AAG1E,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC3J,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC1F,MAAM,MAAM,6BAA6B,GAAG,qBAAqB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE3G,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAWvK;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3J;AAED,0EAA0E;AAC1E,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,6BAA6B,EAAE,CAAC,CAA0O;AACtV,4EAA4E;AAC5E,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,OAAO,CAAC,EAAE,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAgB/R;AAkBD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,6BAA6B,GAAG,MAAM,CAA6E"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, open, readFile, readdir, unlink } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export async function acquireHostRegistration(root, directory, identity) {
|
|
6
|
+
const registration = { owner_id: randomUUID(), ...identity };
|
|
7
|
+
const target = path.join(root, `.host-online-${registration.owner_id}.json`);
|
|
8
|
+
await writeNew(target, registration);
|
|
9
|
+
await directory.sync();
|
|
10
|
+
if (await exists(path.join(root, ".offline-reconciliation.lock"))) {
|
|
11
|
+
await unlink(target);
|
|
12
|
+
await directory.sync();
|
|
13
|
+
throw new Error("wake acceptance store is reserved for offline reconciliation");
|
|
14
|
+
}
|
|
15
|
+
return registration;
|
|
16
|
+
}
|
|
17
|
+
export async function releaseHostRegistration(root, directory, registration) {
|
|
18
|
+
const target = path.join(root, `.host-online-${registration.owner_id}.json`);
|
|
19
|
+
const current = await readRegistration(target);
|
|
20
|
+
if (!sameOwner(current, registration))
|
|
21
|
+
throw new Error("wake acceptance host registration changed");
|
|
22
|
+
await unlink(target);
|
|
23
|
+
await directory.sync();
|
|
24
|
+
}
|
|
25
|
+
/** Admin callers hold their durable offline lease before calling this. */
|
|
26
|
+
export async function listHostRegistrations(root) { const result = []; for (const entry of await readdir(root)) {
|
|
27
|
+
if (/^\.host-online-[0-9a-f-]{36}\.json$/iu.test(entry))
|
|
28
|
+
result.push(await readRegistration(path.join(root, entry)));
|
|
29
|
+
} return result; }
|
|
30
|
+
/** Returns undefined when any registration is live, unknown, or changed. */
|
|
31
|
+
export async function clearDeadHostRegistrations(root, directory, identity, liveness, authorizedCrossNamespace) {
|
|
32
|
+
const cleared = [];
|
|
33
|
+
for (const entry of await readdir(root)) {
|
|
34
|
+
if (!/^\.host-online-[0-9a-f-]{36}\.json$/iu.test(entry))
|
|
35
|
+
continue;
|
|
36
|
+
const target = path.join(root, entry);
|
|
37
|
+
const owner = await readRegistration(target);
|
|
38
|
+
const digest = hostRegistrationDigest(owner);
|
|
39
|
+
if (!sameNamespace(owner, identity) && !authorizedCrossNamespace.has(digest))
|
|
40
|
+
return undefined;
|
|
41
|
+
if (sameNamespace(owner, identity) && await liveness(owner))
|
|
42
|
+
return undefined;
|
|
43
|
+
const checked = await readRegistration(target);
|
|
44
|
+
if (!sameIdentity(checked, owner))
|
|
45
|
+
return undefined;
|
|
46
|
+
await unlink(target);
|
|
47
|
+
await directory.sync();
|
|
48
|
+
cleared.push(digest);
|
|
49
|
+
}
|
|
50
|
+
return cleared;
|
|
51
|
+
}
|
|
52
|
+
async function readRegistration(target) {
|
|
53
|
+
const entry = await lstat(target);
|
|
54
|
+
if (!entry.isFile() || entry.isSymbolicLink() || Number(entry.uid) !== process.getuid?.() || (Number(entry.mode) & 0o777) !== 0o600 || Number(entry.size) > 1024)
|
|
55
|
+
throw new Error("wake acceptance host registration is unsafe");
|
|
56
|
+
const value = JSON.parse((await readFile(target)).toString("utf8"));
|
|
57
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
58
|
+
throw new Error("wake acceptance host registration is invalid");
|
|
59
|
+
const record = value;
|
|
60
|
+
const keys = ["owner_id", "pid", "process_start", "boot_id", "pid_namespace_dev", "pid_namespace_ino"];
|
|
61
|
+
if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.hasOwn(record, key)))
|
|
62
|
+
throw new Error("wake acceptance host registration is invalid");
|
|
63
|
+
const result = { dev: Number(entry.dev), ino: Number(entry.ino), owner_id: uuid(record.owner_id), pid: positive(record.pid), process_start: text(record.process_start), boot_id: text(record.boot_id), pid_namespace_dev: positive(record.pid_namespace_dev), pid_namespace_ino: positive(record.pid_namespace_ino) };
|
|
64
|
+
if (!result.process_start || !result.boot_id)
|
|
65
|
+
throw new Error("wake acceptance host registration is invalid");
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async function writeNew(target, value) { const handle = await open(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow(), 0o600); try {
|
|
69
|
+
await handle.writeFile(JSON.stringify(value));
|
|
70
|
+
await handle.sync();
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await handle.close();
|
|
74
|
+
} }
|
|
75
|
+
async function exists(target) { try {
|
|
76
|
+
await lstat(target);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code === "ENOENT")
|
|
81
|
+
return false;
|
|
82
|
+
throw error;
|
|
83
|
+
} }
|
|
84
|
+
function sameOwner(left, right) { return left.owner_id === right.owner_id && left.pid === right.pid && left.process_start === right.process_start && left.boot_id === right.boot_id && sameNamespace(left, right); }
|
|
85
|
+
function sameIdentity(left, right) { return left.dev === right.dev && left.ino === right.ino && sameOwner(left, right); }
|
|
86
|
+
export function hostRegistrationDigest(value) { return createHash("sha256").update(JSON.stringify(value)).digest("hex"); }
|
|
87
|
+
function sameNamespace(left, right) { return left.pid_namespace_dev === right.pid_namespace_dev && left.pid_namespace_ino === right.pid_namespace_ino; }
|
|
88
|
+
function text(value) { if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > 1024)
|
|
89
|
+
throw new Error("wake acceptance host registration is invalid"); return value; }
|
|
90
|
+
function uuid(value) { const result = text(value); if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(result))
|
|
91
|
+
throw new Error("wake acceptance host registration is invalid"); return result; }
|
|
92
|
+
function positive(value) { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
93
|
+
throw new Error("wake acceptance host registration is invalid"); return value; }
|
|
94
|
+
function noFollow() { return constants.O_NOFOLLOW ?? 0; }
|