diff --git a/src/lock-ops.mjs b/src/lock-ops.mjs index 3010f5a..d0ac6b1 100644 --- a/src/lock-ops.mjs +++ b/src/lock-ops.mjs @@ -61,7 +61,6 @@ function filterActiveLocks(locks) { return locks.filter((lock) => !lock.expiresAt || lock.expiresAt > now); } - /** * Queries relays for active lock events matching the criteria. * Uses a tiered approach: tries primary relays first, then falls back to others if needed. diff --git a/src/lock-publisher.mjs b/src/lock-publisher.mjs index 5d70775..004bcd7 100644 --- a/src/lock-publisher.mjs +++ b/src/lock-publisher.mjs @@ -6,8 +6,9 @@ import { getRelayFallbacks, getMinActiveRelayPool, } from './torch-config.mjs'; +import { mergeRelayList, secureRandom, withTimeout } from './lock-utils.mjs'; import { defaultHealthManager, buildRelayHealthConfig } from './relay-health-manager.mjs'; -import { mergeRelayList, withTimeout, relayListLabel } from './lock-utils.mjs'; +import { relayListLabel } from './utils.mjs'; const PUBLISH_ERROR_CODES = { TIMEOUT: 'publish_timeout', @@ -100,12 +101,6 @@ function isTransientPublishCategory(category) { ].includes(category); } -const MAX_RANDOM = 281474976710655; // 2**48 - 1 - -export function secureRandom() { - return randomInt(0, MAX_RANDOM) / MAX_RANDOM; -} - function calculateBackoffDelayMs(attemptNumber, baseMs, capMs, randomFn = secureRandom) { const maxDelay = Math.min(capMs, baseMs * (2 ** Math.max(0, attemptNumber - 1))); return Math.floor(randomFn() * maxDelay); diff --git a/src/lock-utils.mjs b/src/lock-utils.mjs index 7b4d420..42bddd1 100644 --- a/src/lock-utils.mjs +++ b/src/lock-utils.mjs @@ -3,7 +3,7 @@ import path from 'node:path'; import { randomInt } from 'node:crypto'; import { todayDateStr, getIsoWeekStr } from './utils.mjs'; -export { relayListLabel } from './utils.mjs'; +export { relayListLabel, mergeRelayList } from './utils.mjs'; /** * Scans the local log directory to identify agents that have already completed their task @@ -14,7 +14,25 @@ export { relayListLabel } from './utils.mjs'; * @param {Object} deps - Dependency injection for testing * @returns {Promise>} - Set of agent names that have completed their task */ -export async function getCompletedAgents(cadence, logDir, deps = {}) { +/** + * Promisified timeout wrapper. + * + * @param {Promise} promise - The promise to wrap. + * @param {number} timeoutMs - Timeout in milliseconds. + * @param {string} timeoutMessage - Error message on timeout. + * @returns {Promise} - The result of the promise or a timeout error. + */ +export function withTimeout(promise, timeoutMs, timeoutMessage) { + let timeoutHandle; + const timeoutPromise = new Promise((_, reject) => { + timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); + }); + return Promise.race([promise, timeoutPromise]).finally(() => { + if (timeoutHandle) clearTimeout(timeoutHandle); + }); +} + +export async function getCompletedAgents(cadence, logDir, deps) { const { readdir = fs.readdir, getDateStr = todayDateStr, getIsoWeek = getIsoWeekStr } = deps; const completed = new Set(); const targetDir = path.join(logDir, cadence); @@ -51,21 +69,6 @@ export async function getCompletedAgents(cadence, logDir, deps = {}) { return completed; } -export function withTimeout(promise, timeoutMs, timeoutMessage = 'Operation timed out') { - let timer; - const timeoutPromise = new Promise((_, reject) => { - timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); - }); - return Promise.race([ - promise.finally(() => clearTimeout(timer)), - timeoutPromise, - ]); -} - -export function mergeRelayList(primaryRelays, fallbackRelays) { - return [...new Set([...primaryRelays, ...fallbackRelays])]; -} - const MAX_RANDOM = 281474976710655; // 2**48 - 1 export function secureRandom() { diff --git a/src/relay-health.mjs b/src/relay-health.mjs index 3e4f921..6e09d0b 100644 --- a/src/relay-health.mjs +++ b/src/relay-health.mjs @@ -4,7 +4,7 @@ import WebSocket from 'ws'; import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure'; import { getNamespace, getRelays } from './torch-config.mjs'; import { MS_PER_SECOND } from './constants.mjs'; -import { withTimeout } from './lock-utils.mjs'; +import { withTimeout } from './utils.mjs'; const PROBE_KIND = 27235; diff --git a/src/services/governance/index.js b/src/services/governance/index.js index 87f0974..9aa6778 100644 --- a/src/services/governance/index.js +++ b/src/services/governance/index.js @@ -355,6 +355,9 @@ export async function rollbackPrompt(target, hashOrStrategy = 'latest') { // Fallback to Git try { const commit = hashOrStrategy === 'latest' ? 'HEAD' : hashOrStrategy; + if (commit.startsWith('-')) { + throw new Error(`Invalid commit/strategy: ${commit} (cannot start with '-')`); + } execFileSync('git', ['checkout', commit, '--', target]); return { success: true, source: 'git', restored: commit }; } catch (e) {