@noopolis/daimon 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/agentSkillsDirectory.d.ts +25 -0
- package/dist/runtime/agentSkillsDirectory.d.ts.map +1 -0
- package/dist/runtime/agentSkillsDirectory.js +101 -0
- 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 +15 -2
- package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
- package/dist/runtime/engineBrokerServiceConfig.js +53 -11
- 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 +26 -2
- package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProjection.js +18 -7
- 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 +12 -5
- package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerAttestation.js +5 -3
- package/dist/runtime/grokWorkerHomeAttestation.d.ts +51 -5
- package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerHomeAttestation.js +103 -8
- package/dist/runtime/grokWorkerSkills.d.ts +86 -0
- package/dist/runtime/grokWorkerSkills.d.ts.map +1 -0
- package/dist/runtime/grokWorkerSkills.js +135 -0
- package/dist/runtime/index.d.ts +10 -2
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +8 -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,3 +1,4 @@
|
|
|
1
|
+
import { WakeExecutionClaimLostError, readExpectedClaim, waitForClaimTransitionLock } from "./wakeAcceptanceClaimDiagnostics.js";
|
|
1
2
|
import { sameNamespace, currentProcessIdentity, processIsAlive, WakeTransitionLockBlockedError } from "./wakeAcceptanceProcessIdentity.js";
|
|
2
3
|
import { constants } from "node:fs";
|
|
3
4
|
import { link, lstat, open, readFile, readdir, realpath, rename, unlink } from "node:fs/promises";
|
|
@@ -6,8 +7,9 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
6
7
|
import { MAX_WAKE_ACCEPTANCE_RECORD_BYTES, sanitizeWakeCompletionText, wakeAcceptanceDigest } from "./wakeAcceptanceTypes.js";
|
|
7
8
|
import { sanitizeExecutionError, parseStoredWakeAcceptance, publicStatus } from "./wakeAcceptanceRecord.js";
|
|
8
9
|
import { assertOfflineReconciliationLeaseAvailable } from "./wakeAcceptanceReconciliation.js";
|
|
9
|
-
import { acquireHostRegistration, releaseHostRegistration } from "./storeCoordination.js";
|
|
10
|
-
import {
|
|
10
|
+
import { acquireHostRegistration, pruneDeadHostRegistrations, releaseHostRegistration } from "./storeCoordination.js";
|
|
11
|
+
import { WakeAcceptanceIndex } from "./wakeAcceptanceIndex.js";
|
|
12
|
+
import { MAX_WAKE_ACCEPTANCE_RECORDS, WAKE_ACCEPTANCE_COMPACTION_THRESHOLD, staleQueuedFilesToStop, terminalFilesToCompact } from "./wakeAcceptanceRetention.js";
|
|
11
13
|
const DEFAULT_CLAIM_TTL_MS = 240_000;
|
|
12
14
|
/** Durable, private idempotency authority; callers must pre-create its 0700 root. */
|
|
13
15
|
export class WakeAcceptanceStore {
|
|
@@ -21,7 +23,9 @@ export class WakeAcceptanceStore {
|
|
|
21
23
|
ownerLiveness;
|
|
22
24
|
afterFinalLockAssertion;
|
|
23
25
|
mutations = Promise.resolve();
|
|
24
|
-
|
|
26
|
+
index;
|
|
27
|
+
onRecordRead;
|
|
28
|
+
claimTransitionLockTiming;
|
|
25
29
|
constructor(root, directory, identity, registration, claimTtlMs, now, owner, ownerLiveness, afterFinalLockAssertion) {
|
|
26
30
|
this.root = root;
|
|
27
31
|
this.directory = directory;
|
|
@@ -32,6 +36,8 @@ export class WakeAcceptanceStore {
|
|
|
32
36
|
this.owner = owner;
|
|
33
37
|
this.ownerLiveness = ownerLiveness;
|
|
34
38
|
this.afterFinalLockAssertion = afterFinalLockAssertion;
|
|
39
|
+
this.index = new WakeAcceptanceIndex(root, { directoryStat: async () => await this.directory.stat({ bigint: true }), files: async () => await this.files(), readId: async (file) => (await this.read(file)).acceptance_id, fileStat: async (file) => await lstat(file, { bigint: true }).catch((error) => { if (error.code === "ENOENT")
|
|
40
|
+
return undefined; throw error; }) }, () => BigInt(this.now()) * 1000000n);
|
|
35
41
|
}
|
|
36
42
|
static async open(root, options = {}) {
|
|
37
43
|
const claimTtlMs = options.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS;
|
|
@@ -53,23 +59,27 @@ export class WakeAcceptanceStore {
|
|
|
53
59
|
if (!same(identity(before), identity(after)))
|
|
54
60
|
throw new Error("wake acceptance store changed during validation");
|
|
55
61
|
const registration = await acquireHostRegistration(real, directory, owner);
|
|
56
|
-
|
|
62
|
+
await pruneDeadHostRegistrations(real, directory, registration, options.ownerLiveness ?? processIsAlive);
|
|
63
|
+
const store = new WakeAcceptanceStore(real, directory, identity(before), registration, claimTtlMs, options.nowForTest ?? Date.now, owner, options.ownerLiveness ?? processIsAlive, options.afterFinalLockAssertion);
|
|
64
|
+
store.claimTransitionLockTiming = options.claimTransitionLockTimingForTest;
|
|
65
|
+
store.onRecordRead = options.onRecordReadForTest;
|
|
66
|
+
return store;
|
|
57
67
|
}
|
|
58
68
|
catch (error) {
|
|
59
69
|
await directory.close().catch(() => undefined);
|
|
60
70
|
throw error;
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
|
-
accept(request) {
|
|
64
|
-
return this.serialize(async () => await this.acceptNow(request));
|
|
73
|
+
accept(request, nativeSchedule = false) {
|
|
74
|
+
return this.serialize(async () => await this.acceptNow(request, nativeSchedule));
|
|
65
75
|
}
|
|
66
|
-
async acceptNow(request) {
|
|
76
|
+
async acceptNow(request, nativeSchedule) {
|
|
67
77
|
await this.verify();
|
|
68
78
|
const request_digest = wakeAcceptanceDigest(request);
|
|
69
79
|
const target = this.fileFor(request.agent_id, request.delivery_id);
|
|
70
80
|
const existing = await this.readOptional(target);
|
|
71
81
|
if (existing !== undefined) {
|
|
72
|
-
if (existing.request_digest !== request_digest)
|
|
82
|
+
if (existing.request_digest !== request_digest || existing.native_schedule !== (nativeSchedule ? true : undefined))
|
|
73
83
|
throw new WakeAcceptanceConflictError();
|
|
74
84
|
return { record: existing, created: false };
|
|
75
85
|
}
|
|
@@ -77,7 +87,7 @@ export class WakeAcceptanceStore {
|
|
|
77
87
|
if ((await this.files()).length >= MAX_WAKE_ACCEPTANCE_RECORDS)
|
|
78
88
|
throw new WakeInboxFullError();
|
|
79
89
|
const now = new Date().toISOString();
|
|
80
|
-
const record = { acceptance_id: randomUUID(), agent_id: request.agent_id, delivery_id: request.delivery_id, request_digest, event: request.event, state: "accepted", accepted_at: now, updated_at: now };
|
|
90
|
+
const record = { acceptance_id: randomUUID(), agent_id: request.agent_id, delivery_id: request.delivery_id, request_digest, event: request.event, state: "accepted", accepted_at: now, updated_at: now, ...(nativeSchedule ? { native_schedule: true } : {}) };
|
|
81
91
|
const temporary = path.join(this.root, `.pending-${randomUUID()}`);
|
|
82
92
|
try {
|
|
83
93
|
await this.writeNew(temporary, record);
|
|
@@ -88,12 +98,12 @@ export class WakeAcceptanceStore {
|
|
|
88
98
|
if (error.code !== "EEXIST")
|
|
89
99
|
throw error;
|
|
90
100
|
const winner = await this.read(target);
|
|
91
|
-
if (winner.request_digest !== request_digest)
|
|
101
|
+
if (winner.request_digest !== request_digest || winner.native_schedule !== (nativeSchedule ? true : undefined))
|
|
92
102
|
throw new WakeAcceptanceConflictError();
|
|
93
103
|
return { record: winner, created: false };
|
|
94
104
|
}
|
|
95
105
|
await this.directory.sync();
|
|
96
|
-
this.
|
|
106
|
+
this.index.set(record.acceptance_id, target);
|
|
97
107
|
return { record, created: true };
|
|
98
108
|
}
|
|
99
109
|
finally {
|
|
@@ -123,7 +133,7 @@ export class WakeAcceptanceStore {
|
|
|
123
133
|
...(includeExecutionErrors && record.execution_error !== undefined ? { execution_error: record.execution_error } : {}) };
|
|
124
134
|
});
|
|
125
135
|
}
|
|
126
|
-
async recoverable(agentIds) {
|
|
136
|
+
async recoverable(agentIds, onStale) {
|
|
127
137
|
const result = [];
|
|
128
138
|
for (const file of await this.files()) {
|
|
129
139
|
const record = await this.read(path.join(this.root, file));
|
|
@@ -131,7 +141,12 @@ export class WakeAcceptanceStore {
|
|
|
131
141
|
throw new Error("wake acceptance store contains an unknown agent authority");
|
|
132
142
|
if (record.state === "accepted" || record.state === "running") {
|
|
133
143
|
const claim = await this.readClaimOptional(this.claimFor(record));
|
|
134
|
-
|
|
144
|
+
// A long-parked delivery is no longer offered: it would head every batch ahead of today's mail. Compaction stops it.
|
|
145
|
+
if (staleQueuedFilesToStop([{ file, state: record.state, updatedAt: record.updated_at, acceptedAt: record.accepted_at, acceptanceId: record.acceptance_id, claimed: claim !== undefined && !expired(claim, this.now()) }], this.now()).length) {
|
|
146
|
+
onStale?.(record);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
result.push(!record.deferred && record.execution_id === undefined && record.execution_error === undefined && claim?.acceptance_ids?.includes(record.acceptance_id) && claim.execution_id !== undefined ? { ...record, execution_id: claim.execution_id } : record);
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
152
|
return result.sort((left, right) => left.accepted_at.localeCompare(right.accepted_at) || left.acceptance_id.localeCompare(right.acceptance_id));
|
|
@@ -140,13 +155,14 @@ export class WakeAcceptanceStore {
|
|
|
140
155
|
return this.serialize(async () => await this.transitionNow(acceptanceId, state, code));
|
|
141
156
|
}
|
|
142
157
|
/** Fuse race seam: never stops a record that was claimed after enumeration. */
|
|
143
|
-
transitionAcceptedToStopped(acceptanceId) {
|
|
158
|
+
transitionAcceptedToStopped(acceptanceId, code = "host_stopping") {
|
|
144
159
|
return this.serialize(async () => {
|
|
145
160
|
const target = await this.pathForAcceptanceId(acceptanceId);
|
|
146
161
|
const prior = await this.read(target);
|
|
147
|
-
|
|
162
|
+
const claim = prior.state === "accepted" ? await this.readClaimOptional(this.claimFor(prior)) : undefined;
|
|
163
|
+
if (prior.state !== "accepted" || (claim !== undefined && !expired(claim, this.now())))
|
|
148
164
|
return prior;
|
|
149
|
-
const record = { ...prior, state: "stopped", code
|
|
165
|
+
const record = { ...prior, state: "stopped", code, updated_at: new Date().toISOString() };
|
|
150
166
|
await this.replace(target, record);
|
|
151
167
|
return record;
|
|
152
168
|
});
|
|
@@ -216,13 +232,9 @@ export class WakeAcceptanceStore {
|
|
|
216
232
|
const record = await this.findByAcceptanceId(acceptanceId);
|
|
217
233
|
if (record === undefined)
|
|
218
234
|
throw new Error("wake acceptance receipt is unavailable");
|
|
219
|
-
const lock = await this.acquireTransitionLock(record, claim.owner_id);
|
|
220
|
-
if (lock === undefined)
|
|
221
|
-
throw new WakeExecutionClaimLostError();
|
|
235
|
+
const lock = await waitForClaimTransitionLock(() => this.acquireTransitionLock(record, claim.owner_id), () => this.readLockOptional(this.lockFor(record)), acceptanceId, claim, "renew", this.claimTransitionLockTiming);
|
|
222
236
|
try {
|
|
223
|
-
const current = await this.readClaim(this.claimFor(record));
|
|
224
|
-
if (!(current.acceptance_id === acceptanceId || current.acceptance_ids?.includes(acceptanceId)) || current.acceptance_id !== claim.acceptance_id || current.owner_id !== claim.owner_id || current.generation !== claim.generation || current.expires_at !== claim.expires_at || expired(current, this.now()))
|
|
225
|
-
throw new WakeExecutionClaimLostError();
|
|
237
|
+
const current = await readExpectedClaim(() => this.readClaim(this.claimFor(record)), acceptanceId, claim, "renew", this.now, lock);
|
|
226
238
|
const renewed = { ...current, expires_at: new Date(this.now() + this.claimTtlMs).toISOString() };
|
|
227
239
|
await this.replaceValue(this.claimFor(record), renewed);
|
|
228
240
|
return renewed;
|
|
@@ -240,24 +252,22 @@ export class WakeAcceptanceStore {
|
|
|
240
252
|
const initial = await this.findByAcceptanceId(acceptanceId);
|
|
241
253
|
if (initial === undefined)
|
|
242
254
|
throw new Error("wake acceptance receipt is unavailable");
|
|
243
|
-
const lock = await this.acquireTransitionLock(initial, claim.owner_id);
|
|
244
|
-
if (lock === undefined)
|
|
245
|
-
throw new WakeExecutionClaimLostError();
|
|
255
|
+
const lock = await waitForClaimTransitionLock(() => this.acquireTransitionLock(initial, claim.owner_id), () => this.readLockOptional(this.lockFor(initial)), acceptanceId, claim, `transition:${state}`, this.claimTransitionLockTiming);
|
|
246
256
|
try {
|
|
247
|
-
let record = await this.transitionNowClaimed(acceptanceId, claim);
|
|
257
|
+
let record = await this.transitionNowClaimed(acceptanceId, claim, `transition:${state}:initial`, lock);
|
|
248
258
|
if (isTerminal(record.state))
|
|
249
259
|
return record;
|
|
250
260
|
if (state !== "running" && record.claim_generation !== claim.generation)
|
|
251
|
-
throw new WakeExecutionClaimLostError();
|
|
252
|
-
await this.assertTransitionLock(record, lock);
|
|
253
|
-
record = await this.transitionNowClaimed(acceptanceId, claim);
|
|
261
|
+
throw new WakeExecutionClaimLostError({ step: `transition:${state}`, assertion: "record_claim_generation", acceptance_id: acceptanceId, expected: claim.generation, current: record.claim_generation ?? null, lock });
|
|
262
|
+
await this.assertTransitionLock(record, lock, `transition:${state}`);
|
|
263
|
+
record = await this.transitionNowClaimed(acceptanceId, claim, `transition:${state}:final`, lock);
|
|
254
264
|
if (isTerminal(record.state))
|
|
255
265
|
return record;
|
|
256
266
|
if (state !== "running" && record.claim_generation !== claim.generation)
|
|
257
|
-
throw new WakeExecutionClaimLostError();
|
|
258
|
-
await this.assertTransitionLock(record, lock);
|
|
267
|
+
throw new WakeExecutionClaimLostError({ step: `transition:${state}`, assertion: "record_claim_generation", acceptance_id: acceptanceId, expected: claim.generation, current: record.claim_generation ?? null, lock });
|
|
268
|
+
await this.assertTransitionLock(record, lock, `transition:${state}`);
|
|
259
269
|
await this.afterFinalLockAssertion?.();
|
|
260
|
-
await this.assertTransitionLock(record, lock);
|
|
270
|
+
await this.assertTransitionLock(record, lock, `transition:${state}:before_write`);
|
|
261
271
|
const target = this.fileFor(record.agent_id, record.delivery_id);
|
|
262
272
|
// The code explains the transition that produced the CURRENT state, so a
|
|
263
273
|
// transition that names none clears it. While codes existed only on terminal
|
|
@@ -285,14 +295,12 @@ export class WakeAcceptanceStore {
|
|
|
285
295
|
return this.serialize(async () => {
|
|
286
296
|
const record = await this.findByAcceptanceId(claim.acceptance_id);
|
|
287
297
|
if (!record)
|
|
288
|
-
throw new WakeExecutionClaimLostError();
|
|
289
|
-
const lock = await this.acquireTransitionLock(record, claim.owner_id);
|
|
290
|
-
if (!lock)
|
|
291
|
-
throw new WakeExecutionClaimLostError();
|
|
298
|
+
throw new WakeExecutionClaimLostError({ step: "release", assertion: "record_missing", acceptance_id: claim.acceptance_id, expected: claim, current: null });
|
|
299
|
+
const lock = await waitForClaimTransitionLock(() => this.acquireTransitionLock(record, claim.owner_id), () => this.readLockOptional(this.lockFor(record)), claim.acceptance_id, claim, "release", this.claimTransitionLockTiming);
|
|
292
300
|
try {
|
|
293
301
|
const current = await this.readClaimOptional(this.claimFor(record));
|
|
294
302
|
if (current?.generation !== claim.generation || current.owner_id !== claim.owner_id)
|
|
295
|
-
throw new WakeExecutionClaimLostError();
|
|
303
|
+
throw new WakeExecutionClaimLostError({ step: "release", assertion: "claim_identity", acceptance_id: claim.acceptance_id, expected: claim, current: current ?? null, lock });
|
|
296
304
|
await unlink(this.claimFor(record));
|
|
297
305
|
await this.directory.sync();
|
|
298
306
|
}
|
|
@@ -329,16 +337,8 @@ export class WakeAcceptanceStore {
|
|
|
329
337
|
async findByAcceptanceId(acceptanceId) {
|
|
330
338
|
if (!uuid(acceptanceId))
|
|
331
339
|
return undefined;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return await this.readOptional(known);
|
|
335
|
-
for (const file of await this.files()) {
|
|
336
|
-
const record = await this.read(path.join(this.root, file));
|
|
337
|
-
this.acceptanceFiles.set(record.acceptance_id, path.join(this.root, file));
|
|
338
|
-
if (record.acceptance_id === acceptanceId)
|
|
339
|
-
return record;
|
|
340
|
-
}
|
|
341
|
-
return undefined;
|
|
340
|
+
await this.verify();
|
|
341
|
+
return await this.index.find(acceptanceId, async (file) => await this.readOptional(file));
|
|
342
342
|
}
|
|
343
343
|
async pathForAcceptanceId(acceptanceId) {
|
|
344
344
|
const record = await this.findByAcceptanceId(acceptanceId);
|
|
@@ -352,7 +352,7 @@ export class WakeAcceptanceStore {
|
|
|
352
352
|
const target = this.lockFor(record);
|
|
353
353
|
const current = await this.readLockOptional(target);
|
|
354
354
|
if (current !== undefined && !sameNamespace(current, this.owner))
|
|
355
|
-
throw new WakeTransitionLockBlockedError();
|
|
355
|
+
throw new WakeTransitionLockBlockedError({ step: "acquire_transition_lock", acceptance_id: record.acceptance_id, expected: this.owner, current });
|
|
356
356
|
if (current !== undefined && await this.ownerLiveness(current))
|
|
357
357
|
return undefined;
|
|
358
358
|
if (current !== undefined) {
|
|
@@ -378,10 +378,10 @@ export class WakeAcceptanceStore {
|
|
|
378
378
|
await unlink(temporary).catch(() => undefined);
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
async assertTransitionLock(record, lock) {
|
|
382
|
-
const current = await this.readLock(this.lockFor(record)).catch(() => { throw new WakeExecutionClaimLostError(); });
|
|
381
|
+
async assertTransitionLock(record, lock, step) {
|
|
382
|
+
const current = await this.readLock(this.lockFor(record)).catch((cause) => { throw new WakeExecutionClaimLostError({ step, assertion: "transition_lock_read", acceptance_id: record.acceptance_id, expected: lock, current: null }, cause); });
|
|
383
383
|
if (current.owner_id !== lock.owner_id || current.generation !== lock.generation || current.pid !== lock.pid || current.process_start !== lock.process_start || current.boot_id !== lock.boot_id || !sameNamespace(current, lock))
|
|
384
|
-
throw new WakeExecutionClaimLostError();
|
|
384
|
+
throw new WakeExecutionClaimLostError({ step, assertion: "transition_lock_identity", acceptance_id: record.acceptance_id, expected: lock, current });
|
|
385
385
|
}
|
|
386
386
|
async releaseTransitionLock(record, lock) {
|
|
387
387
|
const target = this.lockFor(record);
|
|
@@ -399,7 +399,7 @@ export class WakeAcceptanceStore {
|
|
|
399
399
|
const entries = await readdir(this.root);
|
|
400
400
|
const files = entries.filter((entry) => /^[a-f0-9]{64}\.json$/.test(entry));
|
|
401
401
|
if (files.length > MAX_WAKE_ACCEPTANCE_RECORDS || entries.length > MAX_WAKE_ACCEPTANCE_RECORDS + 128)
|
|
402
|
-
throw new
|
|
402
|
+
throw new WakeInboxFullError("wake acceptance store exceeds its bounded record limit");
|
|
403
403
|
return files;
|
|
404
404
|
}
|
|
405
405
|
async claimFiles() { return (await readdir(this.root)).filter((entry) => /^[a-f0-9]{64}\.agent-claim$/.test(entry)); }
|
|
@@ -415,6 +415,7 @@ export class WakeAcceptanceStore {
|
|
|
415
415
|
const entry = await lstat(file);
|
|
416
416
|
if (!entry.isFile() || entry.isSymbolicLink() || entry.uid !== process.getuid?.() || (entry.mode & 0o777) !== 0o600 || entry.size > MAX_WAKE_ACCEPTANCE_RECORD_BYTES)
|
|
417
417
|
throw new Error("wake acceptance record is unsafe");
|
|
418
|
+
this.onRecordRead?.(file);
|
|
418
419
|
const bytes = await readFile(file);
|
|
419
420
|
if (bytes.length > MAX_WAKE_ACCEPTANCE_RECORD_BYTES)
|
|
420
421
|
throw new Error("wake acceptance record exceeds its bound");
|
|
@@ -471,25 +472,29 @@ export class WakeAcceptanceStore {
|
|
|
471
472
|
throw new Error("wake transition lock is invalid");
|
|
472
473
|
return result;
|
|
473
474
|
}
|
|
474
|
-
async transitionNowClaimed(acceptanceId, claim) {
|
|
475
|
+
async transitionNowClaimed(acceptanceId, claim, step, lock) {
|
|
475
476
|
const record = await this.findByAcceptanceId(acceptanceId);
|
|
476
477
|
if (record === undefined)
|
|
477
478
|
throw new Error("wake acceptance receipt is unavailable");
|
|
478
|
-
|
|
479
|
-
if (!(current.acceptance_id === acceptanceId || current.acceptance_ids?.includes(acceptanceId)) || current.acceptance_id !== claim.acceptance_id || current.owner_id !== claim.owner_id || current.generation !== claim.generation || current.expires_at !== claim.expires_at || expired(current, this.now()))
|
|
480
|
-
throw new WakeExecutionClaimLostError();
|
|
479
|
+
await readExpectedClaim(() => this.readClaim(this.claimFor(record)), acceptanceId, claim, step, this.now, lock);
|
|
481
480
|
return record;
|
|
482
481
|
}
|
|
483
482
|
async compactTerminalRecords() {
|
|
484
483
|
const files = await this.files();
|
|
485
|
-
if (files.length <
|
|
484
|
+
if (files.length < WAKE_ACCEPTANCE_COMPACTION_THRESHOLD)
|
|
486
485
|
return;
|
|
487
486
|
const records = await Promise.all(files.map(async (file) => ({ file, record: await this.read(path.join(this.root, file)) })));
|
|
487
|
+
const candidates = await Promise.all(records.map(async ({ file, record }) => ({ file, state: record.state, updatedAt: record.updated_at, acceptedAt: record.accepted_at, acceptanceId: record.acceptance_id, claimed: await this.readClaimOptional(this.claimFor(record)).then((claim) => claim !== undefined && !expired(claim, this.now())) })));
|
|
488
|
+
for (const file of staleQueuedFilesToStop(candidates, this.now())) {
|
|
489
|
+
const entry = records.find((candidate) => candidate.file === file);
|
|
490
|
+
entry.record = { ...entry.record, state: "stopped", code: "queued_wake_stopped", updated_at: new Date(this.now()).toISOString() };
|
|
491
|
+
await this.replace(path.join(this.root, file), entry.record);
|
|
492
|
+
}
|
|
488
493
|
for (const file of terminalFilesToCompact(records.map(({ file, record }) => ({ file, state: record.state, updatedAt: record.updated_at, acceptanceId: record.acceptance_id })))) {
|
|
489
494
|
const record = records.find((candidate) => candidate.file === file)?.record;
|
|
490
|
-
if (record !== undefined)
|
|
491
|
-
this.acceptanceFiles.delete(record.acceptance_id);
|
|
492
495
|
await unlink(path.join(this.root, file));
|
|
496
|
+
if (record !== undefined)
|
|
497
|
+
this.index.forget(path.join(this.root, file));
|
|
493
498
|
}
|
|
494
499
|
await this.directory.sync();
|
|
495
500
|
}
|
|
@@ -524,14 +529,12 @@ export class WakeAcceptanceStore {
|
|
|
524
529
|
}
|
|
525
530
|
}
|
|
526
531
|
export class WakeInboxFullError extends Error {
|
|
527
|
-
constructor(
|
|
532
|
+
constructor(message = "wake acceptance store has no capacity without deleting active work") { super(message); }
|
|
528
533
|
}
|
|
529
534
|
export class WakeAcceptanceConflictError extends Error {
|
|
530
535
|
constructor() { super("delivery id is already bound to a different request"); }
|
|
531
536
|
}
|
|
532
|
-
export
|
|
533
|
-
constructor() { super("wake execution claim was lost"); }
|
|
534
|
-
}
|
|
537
|
+
export { WakeExecutionClaimLostError, WakeTransitionLockContentionError } from "./wakeAcceptanceClaimDiagnostics.js";
|
|
535
538
|
/** A container boundary requires deployment-authorized offline reconciliation. */
|
|
536
539
|
export { WakeTransitionLockBlockedError } from "./wakeAcceptanceProcessIdentity.js";
|
|
537
540
|
export { publicAcceptance } from "./wakeAcceptanceRecord.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const WAKE_FUSE_VERSION: "noopolis.daimon.wake-fuse.v1";
|
|
2
2
|
export declare const WAKE_FUSE_DIRECTORY_ENV: "DAIMON_WAKE_FUSE_DIRECTORY";
|
|
3
|
+
export declare const WAKE_FUSE_EPOCH_ZONE_ENV: "DAIMON_WAKE_FUSE_EPOCH_ZONE";
|
|
3
4
|
export declare const DEFAULT_WAKE_FUSE_MAX_WAKES = 100;
|
|
4
5
|
export declare const DEFAULT_WAKE_FUSE_MAX_TOKENS = 5000000;
|
|
5
6
|
export type WakeFuseTripReason = "wake_ceiling" | "token_ceiling" | "operator_stop" | "ledger_unavailable";
|
|
@@ -30,6 +31,26 @@ export type WakeFuseVerdict = Readonly<{
|
|
|
30
31
|
state: "paused";
|
|
31
32
|
reason: "agent_execution_ceiling" | "agent_token_ceiling";
|
|
32
33
|
}>;
|
|
34
|
+
/**
|
|
35
|
+
* The counting window, when the operator has not named one.
|
|
36
|
+
*
|
|
37
|
+
* WHY THIS IS DERIVED AND NOT CONFIGURED
|
|
38
|
+
* --------------------------------------
|
|
39
|
+
* `DAIMON_WAKE_FUSE_EPOCH` is read from the container's env file, and an env
|
|
40
|
+
* file is applied at container CREATION only. So an operator who wants a daily
|
|
41
|
+
* budget has to rewrite that file and RECREATE THE CONTAINER every day, and
|
|
42
|
+
* the only thing that ever did that was a daily rebuild. The budget was
|
|
43
|
+
* therefore coupled to a deploy: stop deploying daily and the wake ceiling
|
|
44
|
+
* never resets, and the organization stops admitting wakes for good.
|
|
45
|
+
*
|
|
46
|
+
* A date-derived epoch removes the coupling instead of moving it. There is no
|
|
47
|
+
* file to rewrite, no deploy, no restart, and no clock for anybody to forget.
|
|
48
|
+
*
|
|
49
|
+
* An explicitly configured epoch still wins, unchanged: an operator pinning one
|
|
50
|
+
* window is doing it deliberately, and this must not roll it out from under
|
|
51
|
+
* them.
|
|
52
|
+
*/
|
|
53
|
+
export declare function derivedEpoch(organizationKey: string, at: Date, zone: string): string;
|
|
33
54
|
export type WakeFuseOptions = Readonly<{
|
|
34
55
|
organizationKey: string;
|
|
35
56
|
environment?: NodeJS.ProcessEnv;
|
|
@@ -45,23 +66,57 @@ export type WakeFuseOptions = Readonly<{
|
|
|
45
66
|
export declare class WakeFuse {
|
|
46
67
|
private readonly armed;
|
|
47
68
|
private readonly directory;
|
|
48
|
-
private
|
|
49
|
-
private
|
|
69
|
+
private epoch;
|
|
70
|
+
private epochStartedAt;
|
|
50
71
|
private readonly maxWakes;
|
|
51
72
|
private readonly maxTokens;
|
|
52
|
-
private
|
|
73
|
+
private admissions;
|
|
53
74
|
private readonly usageLedgerPath;
|
|
54
75
|
private readonly now;
|
|
76
|
+
private readonly nextEpoch;
|
|
55
77
|
private serial;
|
|
56
78
|
private reason;
|
|
57
79
|
private tripMarkerMissing;
|
|
58
80
|
private constructor();
|
|
59
81
|
static open(options: WakeFuseOptions): Promise<WakeFuse>;
|
|
60
82
|
admit(agentId: string, deliveryId: string, limits?: WakeBudgetLimits): Promise<WakeFuseVerdict>;
|
|
83
|
+
/**
|
|
84
|
+
* The budget as the caller should act on it NOW — which means after the day
|
|
85
|
+
* has been allowed to roll.
|
|
86
|
+
*
|
|
87
|
+
* This is the gate the scheduler actually consults: `AttentionDispatcher.drain()`
|
|
88
|
+
* returns at `snapshot().state !== "available"` before it ever reaches
|
|
89
|
+
* `admit()`. Rolling only inside `admitNow` therefore never fired for an
|
|
90
|
+
* organization that had hit its ceiling — it stayed paused across midnight
|
|
91
|
+
* and only a process restart could renew the budget. The roll is serialized
|
|
92
|
+
* with admissions so a snapshot and an admission never see two windows.
|
|
93
|
+
*/
|
|
61
94
|
snapshot(agentId: string, limits?: WakeBudgetLimits): Promise<WakeBudgetSnapshot>;
|
|
95
|
+
private snapshotNow;
|
|
62
96
|
pollOperatorStop(): Promise<WakeFuseTripReason | undefined>;
|
|
63
97
|
tripped(): WakeFuseTripReason | undefined;
|
|
64
98
|
close(): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Moves the counting window when the derived day has changed.
|
|
101
|
+
*
|
|
102
|
+
* Called at admission, which is the only moment the answer can matter: the
|
|
103
|
+
* organization runtime is a long-lived process, so an epoch computed once at
|
|
104
|
+
* open() would be the same epoch a week later.
|
|
105
|
+
*
|
|
106
|
+
* WHAT ROLLS AND WHAT DOES NOT
|
|
107
|
+
* ----------------------------
|
|
108
|
+
* A new window clears a CEILING trip, exactly as selecting a new epoch by
|
|
109
|
+
* hand always did: `readTripMarker` only honours a marker belonging to the
|
|
110
|
+
* current epoch. It does NOT clear an operator stop. `fuse.stop` is checked
|
|
111
|
+
* by existence and is deliberately not epoch-scoped, so a parked newsroom
|
|
112
|
+
* stays parked across midnight. That asymmetry is the whole point: a budget
|
|
113
|
+
* is a thing that renews, an operator stop is a decision that does not.
|
|
114
|
+
*
|
|
115
|
+
* Admissions are rebuilt from the ledger rather than emptied, so a clock that
|
|
116
|
+
* steps BACKWARD into a window that already spent wakes does not hand the
|
|
117
|
+
* organization a fresh budget.
|
|
118
|
+
*/
|
|
119
|
+
private rollEpochIfDue;
|
|
65
120
|
private admitNow;
|
|
66
121
|
private trip;
|
|
67
122
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wakeFuse.d.ts","sourceRoot":"","sources":["../../src/runtime/wakeFuse.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,iBAAiB,EAAG,8BAAuC,CAAC;AACzE,eAAO,MAAM,uBAAuB,EAAG,4BAAqC,CAAC;AAC7E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,4BAA4B,UAAY,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAC3G,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACxF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC/W,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,yBAAyB,GAAG,qBAAqB,CAAA;CAAE,CAAC,CAAC;AACtN,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;GAMG;AACH,qBAAa,QAAQ;IAMjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,
|
|
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"}
|