diff --git a/.scheduler-memory/latest/daily/memories.md b/.scheduler-memory/latest/daily/memories.md index 55bdfd5..b6762ce 100644 --- a/.scheduler-memory/latest/daily/memories.md +++ b/.scheduler-memory/latest/daily/memories.md @@ -1 +1 @@ -- Memory retrieval seed for daily :: Required startup + artifacts + memory + issue capture \ No newline at end of file +- Memory retrieval seed for daily :: scheduler memory retrieval \ No newline at end of file diff --git a/.scheduler-memory/session-mlr0921p/retrieve.json b/.scheduler-memory/session-mlr0921p/retrieve.json new file mode 100644 index 0000000..30c5bcf --- /dev/null +++ b/.scheduler-memory/session-mlr0921p/retrieve.json @@ -0,0 +1,18 @@ +{ + "cadence": "daily", + "operation": "retrieve", + "runId": "session-mlr0921p", + "servicePath": "src/services/memory/index.js#getRelevantMemories", + "inputs": { + "agentId": "scheduler-memory-daily", + "promptPath": "", + "promptIntent": "scheduler memory retrieval", + "query": "scheduler | daily | agent | scheduler-memory-daily | intent | scheduler memory retrieval", + "events": 1 + }, + "outputs": { + "ingestedCount": 1, + "retrievedCount": 1 + }, + "status": "ok" +} \ No newline at end of file diff --git a/.scheduler-memory/session-mlr0921p/retrieve.ok b/.scheduler-memory/session-mlr0921p/retrieve.ok new file mode 100644 index 0000000..84b1719 --- /dev/null +++ b/.scheduler-memory/session-mlr0921p/retrieve.ok @@ -0,0 +1 @@ +MEMORY_RETRIEVED diff --git a/.scheduler-memory/session-mlr0dhq6/retrieve.json b/.scheduler-memory/session-mlr0dhq6/retrieve.json new file mode 100644 index 0000000..5105a48 --- /dev/null +++ b/.scheduler-memory/session-mlr0dhq6/retrieve.json @@ -0,0 +1,18 @@ +{ + "cadence": "daily", + "operation": "retrieve", + "runId": "session-mlr0dhq6", + "servicePath": "src/services/memory/index.js#getRelevantMemories", + "inputs": { + "agentId": "const-refactor-agent", + "promptPath": "src/prompts/daily/const-refactor-agent.md", + "promptIntent": "Required startup + artifacts + memory + issue capture", + "query": "scheduler | daily | agent | const-refactor-agent | intent | Required startup + artifacts + memory + issue capture", + "events": 1 + }, + "outputs": { + "ingestedCount": 1, + "retrievedCount": 1 + }, + "status": "ok" +} \ No newline at end of file diff --git a/.scheduler-memory/session-mlr0dhq6/retrieve.ok b/.scheduler-memory/session-mlr0dhq6/retrieve.ok new file mode 100644 index 0000000..84b1719 --- /dev/null +++ b/.scheduler-memory/session-mlr0dhq6/retrieve.ok @@ -0,0 +1 @@ +MEMORY_RETRIEVED diff --git a/.scheduler-memory/session-mlr0g2tv/store.json b/.scheduler-memory/session-mlr0g2tv/store.json new file mode 100644 index 0000000..f419ee1 --- /dev/null +++ b/.scheduler-memory/session-mlr0g2tv/store.json @@ -0,0 +1,20 @@ +{ + "cadence": "weekly", + "operation": "store", + "runId": "session-mlr0g2tv", + "servicePath": "src/services/memory/index.js#ingestEvents", + "inputs": { + "agentId": "scheduler-memory-weekly", + "promptPath": "", + "promptIntent": "scheduler memory store", + "events": 2, + "sourceFile": "fallback" + }, + "outputs": { + "storedCount": 1, + "summaries": [ + "Store memory event A for weekly :: scheduler memory store Store memory event B for weekly :: scheduler memory store" + ] + }, + "status": "ok" +} \ No newline at end of file diff --git a/.scheduler-memory/session-mlr0g2tv/store.ok b/.scheduler-memory/session-mlr0g2tv/store.ok new file mode 100644 index 0000000..1e40bb6 --- /dev/null +++ b/.scheduler-memory/session-mlr0g2tv/store.ok @@ -0,0 +1 @@ +MEMORY_STORED diff --git a/.scheduler-memory/session-mlr0iplm/store.json b/.scheduler-memory/session-mlr0iplm/store.json new file mode 100644 index 0000000..395e0e7 --- /dev/null +++ b/.scheduler-memory/session-mlr0iplm/store.json @@ -0,0 +1,20 @@ +{ + "cadence": "daily", + "operation": "store", + "runId": "session-mlr0iplm", + "servicePath": "src/services/memory/index.js#ingestEvents", + "inputs": { + "agentId": "const-refactor-agent", + "promptPath": "src/prompts/daily/const-refactor-agent.md", + "promptIntent": "Required startup + artifacts + memory + issue capture", + "events": 1, + "sourceFile": "memory-updates/2026-02-17T19-44-58Z__const-refactor-agent.md" + }, + "outputs": { + "storedCount": 1, + "summaries": [ + "# Memory Update: Constants Refactor\n\n- Discovered hardcoded constants in `src/lock-ops.mjs` duplicating defaults.\n- Extracted `DEFAULT_RETRY_ATTEMPTS`, `DEFAULT_RETRY_BASE_DELAY_MS`, etc. to `src/constants.mjs`.\n- Refactored `src/lock-ops.mjs` to use these constants.\n- Fixed a b…" + ] + }, + "status": "ok" +} \ No newline at end of file diff --git a/.scheduler-memory/session-mlr0iplm/store.ok b/.scheduler-memory/session-mlr0iplm/store.ok new file mode 100644 index 0000000..1e40bb6 --- /dev/null +++ b/.scheduler-memory/session-mlr0iplm/store.ok @@ -0,0 +1 @@ +MEMORY_STORED diff --git a/dashboard.log b/dashboard.log new file mode 100644 index 0000000..b9f5847 --- /dev/null +++ b/dashboard.log @@ -0,0 +1,8 @@ + +> torch-lock@0.1.0 dashboard:serve +> node bin/torch-lock.mjs dashboard + +Dashboard running at http://null:4173/dashboard/ +Authentication: DISABLED (Dashboard is public) +Serving files from /app +Using configuration from /app diff --git a/dashboard_verify.log b/dashboard_verify.log index 739f71f..b9f5847 100644 --- a/dashboard_verify.log +++ b/dashboard_verify.log @@ -2,6 +2,7 @@ > torch-lock@0.1.0 dashboard:serve > node bin/torch-lock.mjs dashboard -Dashboard running at http://localhost:4173/dashboard/ +Dashboard running at http://null:4173/dashboard/ +Authentication: DISABLED (Dashboard is public) Serving files from /app Using configuration from /app diff --git a/memory-updates/2026-02-17T19-44-58Z__const-refactor-agent.md b/memory-updates/2026-02-17T19-44-58Z__const-refactor-agent.md new file mode 100644 index 0000000..3572664 --- /dev/null +++ b/memory-updates/2026-02-17T19-44-58Z__const-refactor-agent.md @@ -0,0 +1,7 @@ +# Memory Update: Constants Refactor + +- Discovered hardcoded constants in `src/lock-ops.mjs` duplicating defaults. +- Extracted `DEFAULT_RETRY_ATTEMPTS`, `DEFAULT_RETRY_BASE_DELAY_MS`, etc. to `src/constants.mjs`. +- Refactored `src/lock-ops.mjs` to use these constants. +- Fixed a bug in `src/cmd-check.mjs` where `queryLocksFn` was undefined. +- Verified with unit tests and lint. diff --git a/package-lock.json b/package-lock.json index 539e8cc..a4947d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@playwright/test": "^1.58.2", "eslint": "^10.0.0", "globals": "^17.3.0" } @@ -255,6 +256,22 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -670,6 +687,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -952,6 +984,38 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index ab950a3..d9dc7d9 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@playwright/test": "^1.58.2", "eslint": "^10.0.0", "globals": "^17.3.0" } diff --git a/perf/constants-refactor/candidates.json b/perf/constants-refactor/candidates.json new file mode 100644 index 0000000..2cd25ba --- /dev/null +++ b/perf/constants-refactor/candidates.json @@ -0,0 +1,1667 @@ +[ + [ + "0", + [ + { + "file": "src/relay-health.mjs", + "line": 24, + "context": "ary?.totalRelays || 0), 0);\n const succe" + }, + { + "file": "src/relay-health.mjs", + "line": 24, + "context": ".totalRelays || 0), 0);\n const successCo" + }, + { + "file": "src/relay-health.mjs", + "line": 25, + "context": "y?.healthyRelays || 0), 0);\n const succe" + }, + { + "file": "src/relay-health.mjs", + "line": 25, + "context": "ealthyRelays || 0), 0);\n const successRa" + }, + { + "file": "src/relay-health.mjs", + "line": 26, + "context": "= relayProbeCount > 0 ? successCount / re" + }, + { + "file": "src/relay-health.mjs", + "line": 29, + "context": "es.length - 1; i >= 0; i -= 1) {\n if (" + }, + { + "file": "src/relay-health.mjs", + "line": 30, + "context": "y?.healthyRelays || 0) > 0) {\n lastH" + }, + { + "file": "src/relay-health.mjs", + "line": 30, + "context": "althyRelays || 0) > 0) {\n lastHealth" + }, + { + "file": "src/relay-health.mjs", + "line": 38, + "context": "null\n : Math.max(0, Math.floor((nowMs" + }, + { + "file": "src/relay-health.mjs", + "line": 61, + "context": "stats.sampleCount > 0 && stats.successRat" + }, + { + "file": "src/relay-health.mjs", + "line": 146, + "context": "onst type = message[0];\n\n if (type =" + }, + { + "file": "src/relay-health.mjs", + "line": 252, + "context": "y.healthyRelays === 0;\n\n const result =" + }, + { + "file": "src/roster.mjs", + "line": 75, + "context": "if (daily.length > 0 && weekly.length >" + }, + { + "file": "src/roster.mjs", + "line": 75, + "context": "&& weekly.length > 0) {\n cachedCano" + }, + { + "file": "src/cmd-check.mjs", + "line": 19, + "context": "usedAgents.length > 0) {\n console.erro" + }, + { + "file": "src/torch-config.mjs", + "line": 20, + "context": "(value) || value <= 0) return null;\n ret" + }, + { + "file": "src/torch-config.mjs", + "line": 27, + "context": "urn parsed.length > 0 ? parsed : null;\n}" + }, + { + "file": "src/torch-config.mjs", + "line": 51, + "context": "(value) || value <= 0) {\n throw new Er" + }, + { + "file": "src/torch-config.mjs", + "line": 94, + "context": "trLock.ttlSeconds > 0\n ? Math.floo" + }, + { + "file": "src/torch-config.mjs", + "line": 239, + "context": "parsed) && parsed > 0) return parsed;\n }" + }, + { + "file": "src/ops.mjs", + "line": 165, + "context": "f (entries.length > 0 && installDir !== '" + }, + { + "file": "src/ops.mjs", + "line": 424, + "context": ";\n let added = 0;\n let updated" + }, + { + "file": "src/ops.mjs", + "line": 425, + "context": "let updated = 0;\n let skipped" + }, + { + "file": "src/ops.mjs", + "line": 426, + "context": "let skipped = 0;\n\n for (const" + }, + { + "file": "src/cli-parser.mjs", + "line": 38, + "context": "ull,\n };\n let i = 0;\n\n if (argv.length" + }, + { + "file": "src/cli-parser.mjs", + "line": 40, + "context": "if (argv.length > 0 && !argv[0].startsW" + }, + { + "file": "src/cli-parser.mjs", + "line": 40, + "context": "length > 0 && !argv[0].startsWith('-')) {" + }, + { + "file": "src/cli-parser.mjs", + "line": 41, + "context": "args.command = argv[0];\n i = 1;\n }" + }, + { + "file": "src/constants.mjs", + "line": 56, + "context": "dex)\n\nExit codes:\n 0 Success\n 1 Usage" + }, + { + "file": "src/utils.mjs", + "line": 2, + "context": "toISOString().slice(0, 10);\n}\n\nexport fun" + }, + { + "file": "src/utils.mjs", + "line": 21, + "context": "the year\n // day: 0 (Sun) -> 6 (Sat). W" + }, + { + "file": "src/utils.mjs", + "line": 25, + "context": "e.getUTCFullYear(), 0, 1));\n const weekN" + }, + { + "file": "src/utils.mjs", + "line": 28, + "context": "eekNo).padStart(2, '0')}`;\n}\n\nexport func" + }, + { + "file": "src/lib.mjs", + "line": 87, + "context": "usedAgents.length > 0) {\n error(`Pau" + }, + { + "file": "src/lib.mjs", + "line": 95, + "context": "pletedAgents.size > 0) {\n error(`Com" + }, + { + "file": "src/lib.mjs", + "line": 207, + "context": "onflicting.length > 0) {\n const earlie" + }, + { + "file": "src/lib.mjs", + "line": 208, + "context": "edAt - b.createdAt)[0];\n\n // Check if" + }, + { + "file": "src/lib.mjs", + "line": 231, + "context": "pubkey: ${pk.slice(0, 16)}...`);\n\n erro" + }, + { + "file": "src/lib.mjs", + "line": 277, + "context": "> 1 && racingLocks[0].eventId !== event." + }, + { + "file": "src/lib.mjs", + "line": 278, + "context": "inner = racingLocks[0];\n error(" + }, + { + "file": "src/lib.mjs", + "line": 341, + "context": "f (locks.length === 0) {\n log(' (no" + }, + { + "file": "src/lib.mjs", + "line": 364, + "context": "lock.eventId?.slice(0, 12)}...`,\n );" + }, + { + "file": "src/lib.mjs", + "line": 374, + "context": "ckedAgents.length > 0) {\n log(` War" + }, + { + "file": "src/lib.mjs", + "line": 620, + "context": "emory.content.slice(0, 200) + '... (trunc" + }, + { + "file": "src/cmd-lock.mjs", + "line": 39, + "context": "onflicting.length > 0) {\n const earlie" + }, + { + "file": "src/cmd-lock.mjs", + "line": 40, + "context": "edAt - b.createdAt)[0];\n console.error" + }, + { + "file": "src/cmd-lock.mjs", + "line": 54, + "context": "pubkey: ${pk.slice(0, 16)}...`);\n\n cons" + }, + { + "file": "src/cmd-lock.mjs", + "line": 99, + "context": "> 1 && racingLocks[0].eventId !== event." + }, + { + "file": "src/cmd-lock.mjs", + "line": 100, + "context": "inner = racingLocks[0];\n console.err" + }, + { + "file": "src/lock-ops.mjs", + "line": 17, + "context": ".tags.find((t) => t[0] === 'd')?.[1] ?? '" + }, + { + "file": "src/lock-ops.mjs", + "line": 18, + "context": ".tags.find((t) => t[0] === 'expiration')?" + }, + { + "file": "src/lock-ops.mjs", + "line": 136, + "context": "return randomInt(0, MAX_RANDOM) / MAX_" + }, + { + "file": "src/lock-ops.mjs", + "line": 140, + "context": "Ms * (2 ** Math.max(0, attemptNumber - 1)" + }, + { + "file": "src/lock-ops.mjs", + "line": 150, + "context": "ue) => sum + value, 0) / latencyEntries.l" + }, + { + "file": "src/lock-ops.mjs", + "line": 152, + "context": "ccessRate = total > 0 ? successCount / to" + }, + { + "file": "src/lock-ops.mjs", + "line": 153, + "context": "meoutRate = total > 0 ? timeoutCount / to" + }, + { + "file": "src/lock-ops.mjs", + "line": 153, + "context": "eoutCount / total : 0;\n const quarantine" + }, + { + "file": "src/lock-ops.mjs", + "line": 154, + "context": "tineUntil - nowMs : 0;\n return {\n rel" + }, + { + "file": "src/lock-ops.mjs", + "line": 162, + "context": "antineRemainingMs > 0,\n quarantineRema" + }, + { + "file": "src/lock-ops.mjs", + "line": 173, + "context": "y.quarantined ? 1 : 0;\n return (summary." + }, + { + "file": "src/lock-ops.mjs", + "line": 180, + "context": "is.lastSnapshotAt = 0;\n }\n\n ensureMetri" + }, + { + "file": "src/lock-ops.mjs", + "line": 189, + "context": "failureStreak: 0,\n quarantine" + }, + { + "file": "src/lock-ops.mjs", + "line": 190, + "context": "quarantineUntil: 0,\n cooldownMs" + }, + { + "file": "src/lock-ops.mjs", + "line": 234, + "context": "..quarantined.slice(0, additionalNeeded))" + }, + { + "file": "src/lock-ops.mjs", + "line": 249, + "context": "centOutcomes.splice(0, metrics.recentOutc" + }, + { + "file": "src/lock-ops.mjs", + "line": 255, + "context": "ics.failureStreak = 0;\n if (metrics." + }, + { + "file": "src/lock-ops.mjs", + "line": 257, + "context": "s.quarantineUntil = 0;\n metrics.co" + }, + { + "file": "src/lock-ops.mjs", + "line": 292, + "context": "is.lastSnapshotAt = 0;\n }\n}\n\nexport cons" + }, + { + "file": "src/lock-ops.mjs", + "line": 335, + "context": "rioritized.length > 0) {\n errorLogge" + }, + { + "file": "src/lock-ops.mjs", + "line": 508, + "context": "backRelays.length > 0) {\n await this" + }, + { + "file": "src/lock-ops.mjs", + "line": 598, + "context": "result.category] || 0) + 1;\n this." + }, + { + "file": "src/cmd-list.mjs", + "line": 29, + "context": "f (locks.length === 0) {\n console.lo" + }, + { + "file": "src/cmd-list.mjs", + "line": 46, + "context": "lock.eventId?.slice(0, 12)}...`,\n );" + }, + { + "file": "src/cmd-list.mjs", + "line": 55, + "context": "ckedAgents.length > 0) {\n console.lo" + }, + { + "file": "src/dashboard.mjs", + "line": 38, + "context": "const type = parts[0];\n const crede" + }, + { + "file": "src/perf/benchmark-config-load.mjs", + "line": 22, + "context": "{\n for (let i = 0; i < ITERATIONS; i+" + } + ] + ], + [ + "1", + [ + { + "file": "src/relay-health.mjs", + "line": 26, + "context": "/ relayProbeCount : 1;\n\n let lastHealthy" + }, + { + "file": "src/relay-health.mjs", + "line": 29, + "context": "oryEntries.length - 1; i >= 0; i -= 1) {" + }, + { + "file": "src/relay-health.mjs", + "line": 29, + "context": "h - 1; i >= 0; i -= 1) {\n if ((history" + }, + { + "file": "src/relay-health.mjs", + "line": 133, + "context": "[event.id], limit: 1 }]));\n });" + }, + { + "file": "src/relay-health.mjs", + "line": 148, + "context": "=== 'OK' && message[1] === event.id) {" + }, + { + "file": "src/relay-health.mjs", + "line": 156, + "context": "'EVENT' && message[1] === subId && messa" + }, + { + "file": "src/ops.mjs", + "line": 126, + "context": "');\n\n try {\n // 1. Install Directory" + }, + { + "file": "src/ops.mjs", + "line": 174, + "context": "installDir) {\n // 1. Copy App Directori" + }, + { + "file": "src/ops.mjs", + "line": 369, + "context": "first.`);\n }\n\n // 1. Backup\n const bac" + }, + { + "file": "src/cli-parser.mjs", + "line": 42, + "context": "= argv[0];\n i = 1;\n }\n\n for (; i <" + }, + { + "file": "src/cli-parser.mjs", + "line": 56, + "context": "le = arg.split('=')[1];\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 60, + "context": "nt = arg.split('=')[1];\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 62, + "context": "ce = arg.split('=')[1];\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 78, + "context": "id = arg.split('=')[1];\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 82, + "context": "pe = arg.split('=')[1];\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 86, + "context": "ring(arg.split('=')[1]).split(',').map((t" + }, + { + "file": "src/cli-parser.mjs", + "line": 91, + "context": "ring(arg.split('=')[1]).toLowerCase();" + }, + { + "file": "src/cli-parser.mjs", + "line": 96, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 100, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 104, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 108, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 112, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 116, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 120, + "context": "loat(arg.split('=')[1]);\n } else if (a" + }, + { + "file": "src/cli-parser.mjs", + "line": 124, + "context": "eInt(arg.split('=')[1], 10);\n } else i" + }, + { + "file": "src/cli-parser.mjs", + "line": 128, + "context": "rm = arg.split('=')[1];\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 132, + "context": "el = arg.split('=')[1];\n }\n }\n\n retu" + }, + { + "file": "src/constants.mjs", + "line": 12, + "context": "CESSFUL_PUBLISHES = 1;\nexport const DEFAU" + }, + { + "file": "src/constants.mjs", + "line": 13, + "context": "ACTIVE_RELAY_POOL = 1;\nexport const VALID" + }, + { + "file": "src/constants.mjs", + "line": 57, + "context": "des:\n 0 Success\n 1 Usage error\n 2 R" + }, + { + "file": "src/utils.mjs", + "line": 21, + "context": "-> 6 (Sat). We want 1 (Mon) -> 7 (Sun)" + }, + { + "file": "src/utils.mjs", + "line": 25, + "context": "etUTCFullYear(), 0, 1));\n const weekNo =" + }, + { + "file": "src/utils.mjs", + "line": 26, + "context": "tart) / 86400000) + 1) / 7);\n\n return `$" + }, + { + "file": "src/lib.mjs", + "line": 51, + "context": "m three sources:\n * 1. Configuration (pau" + }, + { + "file": "src/lib.mjs", + "line": 149, + "context": "*\n * Algorithm:\n * 1. Validate agent aga" + }, + { + "file": "src/lib.mjs", + "line": 200, + "context": "throw new ExitError(1, 'Agent not in rost" + }, + { + "file": "src/lib.mjs", + "line": 203, + "context": "}\n\n error('Step 1: Checking for exist" + }, + { + "file": "src/lib.mjs", + "line": 277, + "context": "acingLocks.length > 1 && racingLocks[0].e" + }, + { + "file": "src/lib.mjs", + "line": 388, + "context": "* This function:\n * 1. Verifies that the" + }, + { + "file": "src/lib.mjs", + "line": 425, + "context": "oin(', ')}`);\n\n // 1. Find existing lock" + }, + { + "file": "src/lib.mjs", + "line": 432, + "context": "throw new ExitError(1, 'No active lock fo" + }, + { + "file": "src/lib.mjs", + "line": 444, + "context": "Iso;\n\n error('Step 1: Generating complet" + }, + { + "file": "src/lib.mjs", + "line": 510, + "context": "throw new ExitError(1, 'No command specif" + }, + { + "file": "src/lib.mjs", + "line": 517, + "context": "throw new ExitError(1, 'Missing cadence')" + }, + { + "file": "src/lib.mjs", + "line": 532, + "context": "throw new ExitError(1, 'Missing agent');" + }, + { + "file": "src/lib.mjs", + "line": 536, + "context": "throw new ExitError(1, 'Missing cadence')" + }, + { + "file": "src/lib.mjs", + "line": 549, + "context": "throw new ExitError(1, 'Missing agent');" + }, + { + "file": "src/lib.mjs", + "line": 553, + "context": "throw new ExitError(1, 'Missing cadence')" + }, + { + "file": "src/lib.mjs", + "line": 566, + "context": "throw new ExitError(1, 'Invalid cadence')" + }, + { + "file": "src/lib.mjs", + "line": 575, + "context": "throw new ExitError(1, 'Missing cadence')" + }, + { + "file": "src/lib.mjs", + "line": 632, + "context": "throw new ExitError(1, 'Missing memory id" + }, + { + "file": "src/lib.mjs", + "line": 642, + "context": "throw new ExitError(1, 'Missing memory id" + }, + { + "file": "src/lib.mjs", + "line": 652, + "context": "throw new ExitError(1, 'Missing memory id" + }, + { + "file": "src/lib.mjs", + "line": 674, + "context": "throw new ExitError(1, 'Unknown command')" + }, + { + "file": "src/cmd-lock.mjs", + "line": 32, + "context": "throw new ExitError(1, 'Agent not in rost" + }, + { + "file": "src/cmd-lock.mjs", + "line": 35, + "context": "console.error('Step 1: Checking for exist" + }, + { + "file": "src/cmd-lock.mjs", + "line": 99, + "context": "acingLocks.length > 1 && racingLocks[0].e" + }, + { + "file": "src/lock-ops.mjs", + "line": 17, + "context": "=> t[0] === 'd')?.[1] ?? '';\n const exp" + }, + { + "file": "src/lock-ops.mjs", + "line": 18, + "context": "=== 'expiration')?.[1];\n const expiresAt" + }, + { + "file": "src/lock-ops.mjs", + "line": 133, + "context": "6710655; // 2**48 - 1\n\nfunction secureRan" + }, + { + "file": "src/lock-ops.mjs", + "line": 140, + "context": "(0, attemptNumber - 1)));\n return Math.f" + }, + { + "file": "src/lock-ops.mjs", + "line": 173, + "context": "mmary.quarantined ? 1 : 0;\n return (summ" + }, + { + "file": "src/lock-ops.mjs", + "line": 213, + "context": "mmary.quarantined ? 1 : -1;\n if (a.s" + }, + { + "file": "src/lock-ops.mjs", + "line": 213, + "context": ".quarantined ? 1 : -1;\n if (a.summar" + }, + { + "file": "src/lock-ops.mjs", + "line": 215, + "context": "Ms === null) return 1;\n if (b.summ" + }, + { + "file": "src/lock-ops.mjs", + "line": 216, + "context": "s === null) return -1;\n return a.s" + }, + { + "file": "src/lock-ops.mjs", + "line": 224, + "context": "inActive = Math.max(1, Math.min(config.mi" + }, + { + "file": "src/lock-ops.mjs", + "line": 224, + "context": "l, relays.length || 1));\n const ranked" + }, + { + "file": "src/lock-ops.mjs", + "line": 263, + "context": "cs.failureStreak += 1;\n if (metrics.fa" + }, + { + "file": "src/lock-ops.mjs", + "line": 305, + "context": "RelayPool: Math.max(1, deps.minActiveRela" + }, + { + "file": "src/lock-ops.mjs", + "line": 465, + "context": "let attemptNumber = 1; attemptNumber <= t" + }, + { + "file": "src/lock-ops.mjs", + "line": 465, + "context": "s; attemptNumber += 1) {\n const st" + }, + { + "file": "src/lock-ops.mjs", + "line": 598, + "context": "t.category] || 0) + 1;\n this.telem" + }, + { + "file": "src/dashboard.mjs", + "line": 39, + "context": "credentials = parts[1];\n let isValid" + } + ] + ], + [ + "2", + [ + { + "file": "src/relay-health.mjs", + "line": 101, + "context": ".toString(16).slice(2, 10)}`;\n const pro" + }, + { + "file": "src/relay-health.mjs", + "line": 145, + "context": "|| message.length < 2) return;\n cons" + }, + { + "file": "src/relay-health.mjs", + "line": 149, + "context": "k = Boolean(message[2]);\n if (!okA" + }, + { + "file": "src/relay-health.mjs", + "line": 156, + "context": "== subId && message[2]?.id === event.id)" + }, + { + "file": "src/cmd-check.mjs", + "line": 50, + "context": "ngify(result, null, 2));\n return result;" + }, + { + "file": "src/ops.mjs", + "line": 135, + "context": "defaultDir;\n\n // 2. Namespace\n cons" + }, + { + "file": "src/ops.mjs", + "line": 177, + "context": "r, 'Copied');\n\n // 2. Copy App Files\n s" + }, + { + "file": "src/ops.mjs", + "line": 268, + "context": "y(configData, null, 2), 'utf8');\n consol" + }, + { + "file": "src/ops.mjs", + "line": 330, + "context": "tringify(pkg, null, 2), 'utf8');" + }, + { + "file": "src/ops.mjs", + "line": 387, + "context": "true });\n }\n\n // 2. Update App Directo" + }, + { + "file": "src/cli-parser.mjs", + "line": 6, + "context": "process.argv.slice(2)\n * @returns {Objec" + }, + { + "file": "src/constants.mjs", + "line": 58, + "context": "1 Usage error\n 2 Relay/network erro" + }, + { + "file": "src/utils.mjs", + "line": 28, + "context": "ng(weekNo).padStart(2, '0')}`;\n}\n\nexport" + }, + { + "file": "src/lib.mjs", + "line": 52, + "context": "(paused agents)\n * 2. Local logs (comple" + }, + { + "file": "src/lib.mjs", + "line": 130, + "context": "ngify(result, null, 2);\n\n if (jsonFile)" + }, + { + "file": "src/lib.mjs", + "line": 150, + "context": "inst the roster.\n * 2. Query relays for e" + }, + { + "file": "src/lib.mjs", + "line": 228, + "context": "}\n\n error('Step 2: Generating ephemer" + }, + { + "file": "src/lib.mjs", + "line": 389, + "context": "ds a valid lock.\n * 2. Publishes a replac" + }, + { + "file": "src/lib.mjs", + "line": 441, + "context": "ventId };\n }\n\n // 2. Build completion e" + }, + { + "file": "src/lib.mjs", + "line": 477, + "context": ") {\n error('Step 2: [DRY RUN] Skipping" + }, + { + "file": "src/lib.mjs", + "line": 479, + "context": "e {\n error('Step 2: Publishing complet" + }, + { + "file": "src/lib.mjs", + "line": 502, + "context": "process.argv.slice(2)\n */\nexport async f" + }, + { + "file": "src/lib.mjs", + "line": 587, + "context": "ngify(result, null, 2));\n if (!res" + }, + { + "file": "src/lib.mjs", + "line": 588, + "context": "throw new ExitError(2, 'Relay health chec" + }, + { + "file": "src/lib.mjs", + "line": 625, + "context": "ngify(result, null, 2));\n break;" + }, + { + "file": "src/lib.mjs", + "line": 635, + "context": "ngify(result, null, 2));\n break;" + }, + { + "file": "src/lib.mjs", + "line": 645, + "context": "ngify(result, null, 2));\n break;" + }, + { + "file": "src/lib.mjs", + "line": 655, + "context": "ngify(result, null, 2));\n break;" + }, + { + "file": "src/lib.mjs", + "line": 661, + "context": "ngify(result, null, 2));\n break;" + }, + { + "file": "src/lib.mjs", + "line": 667, + "context": "ngify(result, null, 2));\n break;" + }, + { + "file": "src/lib.mjs", + "line": 681, + "context": "process.exit(2);\n }\n }\n}" + }, + { + "file": "src/lock-utils.mjs", + "line": 26, + "context": "e.match(/^(\\d{4}-\\d{2}-\\d{2})T.*__([a-zA-" + }, + { + "file": "src/lock-utils.mjs", + "line": 26, + "context": "h(/^(\\d{4}-\\d{2}-\\d{2})T.*__([a-zA-Z0-9-_" + }, + { + "file": "src/cmd-lock.mjs", + "line": 51, + "context": "console.error('Step 2: Generating ephemer" + }, + { + "file": "src/lock-ops.mjs", + "line": 133, + "context": "281474976710655; // 2**48 - 1\n\nfunction s" + }, + { + "file": "src/lock-ops.mjs", + "line": 140, + "context": "in(capMs, baseMs * (2 ** Math.max(0, atte" + }, + { + "file": "src/dashboard.mjs", + "line": 115, + "context": "y(safeConfig, null, 2));\n return" + }, + { + "file": "src/perf/benchmark-config-load.mjs", + "line": 15, + "context": "s\"\n }\n }, null, 2));\n}\n\nconst ITERATI" + }, + { + "file": "src/perf/benchmark-config-load.mjs", + "line": 36, + "context": "${duration.toFixed(2)}ms`);\n console." + } + ] + ], + [ + "10", + [ + { + "file": "src/relay-health.mjs", + "line": 101, + "context": "String(16).slice(2, 10)}`;\n const probeTa" + }, + { + "file": "src/relay-health.mjs", + "line": 200, + "context": "RelaysDownMinutes = 10,\n minSuccessRate" + }, + { + "file": "src/torch-config.mjs", + "line": 185, + "context": "parseInt(envValue, 10);\n if (Number.isNa" + }, + { + "file": "src/torch-config.mjs", + "line": 238, + "context": "= parseInt(envTtl, 10);\n if (!Number.i" + }, + { + "file": "src/cli-parser.mjs", + "line": 68, + "context": "parseInt(argv[++i], 10) || DEFAULT_DASHBOA" + }, + { + "file": "src/cli-parser.mjs", + "line": 94, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 96, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 98, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 100, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 102, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 104, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 106, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 108, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 110, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 112, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 114, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 116, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 122, + "context": "parseInt(argv[++i], 10);\n } else if (ar" + }, + { + "file": "src/cli-parser.mjs", + "line": 124, + "context": "(arg.split('=')[1], 10);\n } else if (ar" + }, + { + "file": "src/utils.mjs", + "line": 2, + "context": "SOString().slice(0, 10);\n}\n\nexport functio" + }, + { + "file": "src/lock-ops.mjs", + "line": 19, + "context": "? parseInt(expTag, 10) : null;\n\n let con" + } + ] + ], + [ + "4", + [ + { + "file": "src/relay-health.mjs", + "line": 65, + "context": "successRate.toFixed(4)),\n windowMinu" + }, + { + "file": "src/ops.mjs", + "line": 136, + "context": "crypto.randomBytes(4).toString('hex');" + }, + { + "file": "src/ops.mjs", + "line": 189, + "context": "ster)}`);\n }\n\n // 4. Copy Static Files" + }, + { + "file": "src/ops.mjs", + "line": 393, + "context": ", 'Updated');\n\n // 4. Update Static File" + }, + { + "file": "src/utils.mjs", + "line": 23, + "context": "date.getUTCDate() + 4 - day);\n\n const ye" + }, + { + "file": "src/lib.mjs", + "line": 152, + "context": "nt (kind 30078).\n * 4. Publish the lock e" + }, + { + "file": "src/lib.mjs", + "line": 263, + "context": ") {\n error('Step 4: [DRY RUN] Skipping" + }, + { + "file": "src/lib.mjs", + "line": 266, + "context": "e {\n error('Step 4: Publishing to rela" + }, + { + "file": "src/lib.mjs", + "line": 361, + "context": "ng(ageMin).padStart(4)}m ` +\n `" + }, + { + "file": "src/lib.mjs", + "line": 362, + "context": "remainMin).padStart(4)} ` +\n `p" + }, + { + "file": "src/lock-utils.mjs", + "line": 26, + "context": "= file.match(/^(\\d{4}-\\d{2}-\\d{2})T.*__(" + }, + { + "file": "src/cmd-lock.mjs", + "line": 85, + "context": "console.error('Step 4: [DRY RUN] Skipping" + }, + { + "file": "src/cmd-lock.mjs", + "line": 88, + "context": "console.error('Step 4: Publishing to rela" + }, + { + "file": "src/lock-ops.mjs", + "line": 274, + "context": "entry.score.toFixed(4)),\n ...entry.s" + }, + { + "file": "src/lock-ops.mjs", + "line": 427, + "context": "ps.retryAttempts || 4;\n this.retryBase" + }, + { + "file": "src/cmd-list.mjs", + "line": 43, + "context": "ng(ageMin).padStart(4)}m ` +\n `" + }, + { + "file": "src/cmd-list.mjs", + "line": 44, + "context": "remainMin).padStart(4)}m ` +\n `" + }, + { + "file": "src/perf/benchmark-config-load.mjs", + "line": 37, + "context": "ITERATIONS).toFixed(4)}ms per load`);" + } + ] + ], + [ + "1000", + [ + { + "file": "src/relay-health.mjs", + "line": 100, + "context": ".floor(Date.now() / 1000);\n const nonce = `" + }, + { + "file": "src/utils.mjs", + "line": 6, + "context": ".floor(Date.now() / 1000);\n}\n\nexport functio" + }, + { + "file": "src/lib.mjs", + "line": 193, + "context": "ew Date(expiresAt * 1000).toISOString()}`);" + }, + { + "file": "src/lib.mjs", + "line": 253, + "context": "dAt: new Date(now * 1000).toISOString()," + }, + { + "file": "src/lib.mjs", + "line": 254, + "context": "ew Date(expiresAt * 1000).toISOString()," + }, + { + "file": "src/lib.mjs", + "line": 299, + "context": "ew Date(expiresAt * 1000).toISOString()}`);" + }, + { + "file": "src/lib.mjs", + "line": 468, + "context": "dAt: new Date(now * 1000).toISOString()," + }, + { + "file": "src/cmd-lock.mjs", + "line": 25, + "context": "ew Date(expiresAt * 1000).toISOString()}`);" + }, + { + "file": "src/cmd-lock.mjs", + "line": 75, + "context": "dAt: new Date(now * 1000).toISOString()," + }, + { + "file": "src/cmd-lock.mjs", + "line": 76, + "context": "ew Date(expiresAt * 1000).toISOString()," + }, + { + "file": "src/cmd-lock.mjs", + "line": 120, + "context": "ew Date(expiresAt * 1000).toISOString()}`);" + }, + { + "file": "src/lock-ops.mjs", + "line": 13, + "context": ".floor(Date.now() / 1000);\n}\n\nexport functio" + }, + { + "file": "src/lock-ops.mjs", + "line": 35, + "context": "(event.created_at * 1000).toISOString()," + }, + { + "file": "src/lock-ops.mjs", + "line": 37, + "context": "ew Date(expiresAt * 1000).toISOString() : nu" + }, + { + "file": "src/perf/benchmark-config-load.mjs", + "line": 18, + "context": "const ITERATIONS = 1000;\nconst start = proc" + } + ] + ], + [ + "3", + [ + { + "file": "src/relay-health.mjs", + "line": 151, + "context": "t: ${String(message[3] || 'unknown reason" + }, + { + "file": "src/ops.mjs", + "line": 141, + "context": "tNamespace;\n\n // 3. Relays\n console" + }, + { + "file": "src/ops.mjs", + "line": 182, + "context": "installDir) {\n // 3. Copy Roster\n cons" + }, + { + "file": "src/ops.mjs", + "line": 390, + "context": ", 'Updated');\n\n // 3. Update App Files (" + }, + { + "file": "src/constants.mjs", + "line": 59, + "context": "lay/network error\n 3 Lock denied (alrea" + }, + { + "file": "src/lib.mjs", + "line": 53, + "context": "ompleted agents)\n * 3. Nostr relays (curr" + }, + { + "file": "src/lib.mjs", + "line": 151, + "context": "for conflicts).\n * 3. Generate a new eph" + }, + { + "file": "src/lib.mjs", + "line": 215, + "context": "throw new ExitError(3, 'Task already comp" + }, + { + "file": "src/lib.mjs", + "line": 225, + "context": "throw new ExitError(3, 'Lock denied');" + }, + { + "file": "src/lib.mjs", + "line": 233, + "context": ".`);\n\n error('Step 3: Building lock even" + }, + { + "file": "src/lib.mjs", + "line": 285, + "context": "throw new ExitError(3, 'Race check lost')" + }, + { + "file": "src/cmd-lock.mjs", + "line": 48, + "context": "throw new ExitError(3, 'Lock denied');" + }, + { + "file": "src/cmd-lock.mjs", + "line": 56, + "context": "console.error('Step 3: Building lock even" + }, + { + "file": "src/cmd-lock.mjs", + "line": 107, + "context": "throw new ExitError(3, 'Race check lost')" + }, + { + "file": "src/lock-ops.mjs", + "line": 301, + "context": "failureThreshold ?? 3,\n quarantineCool" + } + ] + ], + [ + "60", + [ + { + "file": "src/relay-health.mjs", + "line": 21, + "context": "(), windowMinutes = 60 } = {}) {\n const w" + }, + { + "file": "src/relay-health.mjs", + "line": 202, + "context": "windowMinutes = 60,\n } = options;" + }, + { + "file": "src/lib.mjs", + "line": 349, + "context": "= Math.round(age / 60);\n\n let remain" + }, + { + "file": "src/lib.mjs", + "line": 356, + "context": "h.round(remaining / 60);\n }\n\n lo" + }, + { + "file": "src/cmd-list.mjs", + "line": 37, + "context": "= Math.round(age / 60);\n const remai" + }, + { + "file": "src/cmd-list.mjs", + "line": 39, + "context": "h.round(remaining / 60) : '?';\n\n cons" + } + ] + ], + [ + "5", + [ + { + "file": "src/ops.mjs", + "line": 199, + "context": "`);\n }\n }\n\n // 5. Copy Prompts\n for" + }, + { + "file": "src/ops.mjs", + "line": 404, + "context": "`);\n }\n }\n\n // 5. Update Roster (Pre" + }, + { + "file": "src/lib.mjs", + "line": 153, + "context": "event to relays.\n * 5. Wait for propagati" + }, + { + "file": "src/lib.mjs", + "line": 269, + "context": ");\n\n error('Step 5: Race check...');" + }, + { + "file": "src/cmd-lock.mjs", + "line": 91, + "context": "console.error('Step 5: Race check...');" + }, + { + "file": "src/lock-ops.mjs", + "line": 303, + "context": "antineCooldownMs ?? 5 * 60_000,\n snaps" + } + ] + ], + [ + "7", + [ + { + "file": "src/ops.mjs", + "line": 273, + "context": "installDir) {\n // 7. Inject Scripts int" + }, + { + "file": "src/utils.mjs", + "line": 21, + "context": "We want 1 (Mon) -> 7 (Sun)\n const day =" + }, + { + "file": "src/utils.mjs", + "line": 22, + "context": "date.getUTCDay() || 7;\n date.setUTCDate(" + }, + { + "file": "src/utils.mjs", + "line": 26, + "context": "/ 86400000) + 1) / 7);\n\n return `${date" + } + ] + ], + [ + "72", + [ + { + "file": "src/lib.mjs", + "line": 337, + "context": "log(`\\n${'='.repeat(72)}`);\n log(`Activ" + }, + { + "file": "src/lib.mjs", + "line": 339, + "context": "log('='.repeat(72));\n\n if (locks.l" + }, + { + "file": "src/cmd-list.mjs", + "line": 25, + "context": "log(`\\n${'='.repeat(72)}`);\n console.lo" + }, + { + "file": "src/cmd-list.mjs", + "line": 27, + "context": "sole.log('='.repeat(72));\n\n if (locks.l" + } + ] + ], + [ + "200", + [ + { + "file": "src/lib.mjs", + "line": 619, + "context": "ry.content.length > 200) {\n me" + }, + { + "file": "src/lib.mjs", + "line": 620, + "context": "ry.content.slice(0, 200) + '... (truncated," + }, + { + "file": "src/dashboard.mjs", + "line": 114, + "context": "res.writeHead(200, { 'Content-Type':" + }, + { + "file": "src/dashboard.mjs", + "line": 183, + "context": "res.writeHead(200, { 'Content-Type':" + } + ] + ], + [ + "16", + [ + { + "file": "src/relay-health.mjs", + "line": 101, + "context": "h.random().toString(16).slice(2, 10)}`;" + }, + { + "file": "src/lib.mjs", + "line": 231, + "context": "bkey: ${pk.slice(0, 16)}...`);\n\n error('S" + }, + { + "file": "src/cmd-lock.mjs", + "line": 54, + "context": "bkey: ${pk.slice(0, 16)}...`);\n\n console." + } + ] + ], + [ + "6", + [ + { + "file": "src/ops.mjs", + "line": 218, + "context": "ace, relays) {\n // 6. Create/Update torc" + }, + { + "file": "src/ops.mjs", + "line": 415, + "context": "');\n }\n }\n\n // 6. Update Prompts (Co" + }, + { + "file": "src/utils.mjs", + "line": 21, + "context": "// day: 0 (Sun) -> 6 (Sat). We want 1 (M" + } + ] + ], + [ + "500", + [ + { + "file": "src/lock-ops.mjs", + "line": 428, + "context": "retryBaseDelayMs || 500;\n this.retryCapD" + }, + { + "file": "src/dashboard.mjs", + "line": 119, + "context": "res.writeHead(500);\n res.end" + }, + { + "file": "src/dashboard.mjs", + "line": 188, + "context": "res.writeHead(500);\n res.end('" + } + ] + ], + [ + "7200", + [ + { + "file": "src/constants.mjs", + "line": 8, + "context": "const DEFAULT_TTL = 7200;\nexport const DEFAU" + }, + { + "file": "src/constants.mjs", + "line": 48, + "context": "n seconds (default: 7200)\n NOSTR_LOCK_QUERY" + } + ] + ], + [ + "30078", + [ + { + "file": "src/constants.mjs", + "line": 15, + "context": "nst KIND_APP_DATA = 30078;\n\nexport const USAG" + }, + { + "file": "src/lib.mjs", + "line": 151, + "context": "a lock event (kind 30078).\n * 4. Publish the" + } + ] + ], + [ + "30", + [ + { + "file": "src/lib.mjs", + "line": 360, + "context": "? 'unknown').padEnd(30)} ` +\n `ag" + }, + { + "file": "src/cmd-list.mjs", + "line": 42, + "context": "? 'unknown').padEnd(30)} ` +\n `ag" + } + ] + ], + [ + "12", + [ + { + "file": "src/lib.mjs", + "line": 364, + "context": "k.eventId?.slice(0, 12)}...`,\n );" + }, + { + "file": "src/cmd-list.mjs", + "line": 46, + "context": "k.eventId?.slice(0, 12)}...`,\n );" + } + ] + ], + [ + "0.1", + [ + { + "file": "src/lock-ops.mjs", + "line": 171, + "context": "cyMs === null\n ? 0.1\n : Math.min(0.35" + }, + { + "file": "src/dashboard.mjs", + "line": 21, + "context": "PORT, host = '127.0.0.1') {\n // Resolve pa" + } + ] + ], + [ + "25", + [ + { + "file": "src/lock-ops.mjs", + "line": 300, + "context": "ollingWindowSize ?? 25,\n failureThresho" + }, + { + "file": "src/lock-ops.mjs", + "line": 450, + "context": "rollingWindowSize: 25,\n minActiveRel" + } + ] + ], + [ + "401", + [ + { + "file": "src/dashboard.mjs", + "line": 33, + "context": "res.writeHead(401, { 'WWW-Authenticat" + }, + { + "file": "src/dashboard.mjs", + "line": 51, + "context": "res.writeHead(401, { 'WWW-Authenticat" + } + ] + ], + [ + "404", + [ + { + "file": "src/dashboard.mjs", + "line": 124, + "context": "res.writeHead(404);\n res.end('" + }, + { + "file": "src/dashboard.mjs", + "line": 162, + "context": "res.writeHead(404);\n res.end('No" + } + ] + ], + [ + "0.0", + [ + { + "file": "src/dashboard.mjs", + "line": 196, + "context": "`http://${host === '0.0.0.0' ? 'localhost'" + }, + { + "file": "src/dashboard.mjs", + "line": 196, + "context": "p://${host === '0.0.0.0' ? 'localhost' : ho" + } + ] + ] +] diff --git a/scripts/agent/debug_frontend.mjs b/scripts/agent/debug_frontend.mjs new file mode 100644 index 0000000..78a9688 --- /dev/null +++ b/scripts/agent/debug_frontend.mjs @@ -0,0 +1,49 @@ +import { chromium } from '@playwright/test'; + +(async () => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + + let errorCount = 0; + + page.on('console', msg => { + const type = msg.type(); + if (type === 'error' || type === 'warning') { + console.log(`CONSOLE [${type.toUpperCase()}]: ${msg.text()}`); + if (type === 'error') errorCount++; + } + }); + + page.on('pageerror', exception => { + console.log(`PAGE ERROR: ${exception}`); + errorCount++; + }); + + page.on('requestfailed', request => { + // Check if failure() is not null before accessing errorText + const failure = request.failure(); + const errorText = failure ? failure.errorText : 'Unknown error'; + console.log(`REQUEST FAILED: ${request.url()} - ${errorText}`); + errorCount++; + }); + + try { + console.log('Navigating to http://localhost:4173/dashboard/ ...'); + await page.goto('http://localhost:4173/dashboard/'); + console.log('Page loaded. Waiting 5s for any async errors...'); + await page.waitForTimeout(5000); + } catch (e) { + console.error('Navigation failed:', e); + errorCount++; + } + + await browser.close(); + + if (errorCount > 0) { + console.log(`\nFound ${errorCount} errors.`); + process.exit(1); + } else { + console.log('\nNo errors found.'); + process.exit(0); + } +})(); diff --git a/scripts/agent/run-scheduler-cycle.mjs b/scripts/agent/run-scheduler-cycle.mjs index 81c0d86..69d2ff3 100644 --- a/scripts/agent/run-scheduler-cycle.mjs +++ b/scripts/agent/run-scheduler-cycle.mjs @@ -79,11 +79,46 @@ function parseJsonEventsFromOutput(text) { return events; } +const ALLOWED_ENV_KEYS = new Set([ + 'PATH', 'Path', // Windows compatibility + 'HOME', 'USER', 'SHELL', 'TERM', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TZ', + 'NODE_ENV', 'NODE_OPTIONS', 'NODE_PATH', + 'APPDATA', 'LOCALAPPDATA', 'PROGRAMDATA', 'SystemRoot', 'windir', 'ComSpec', 'PATHEXT', + 'TMPDIR', 'TEMP', 'TMP', + 'EDITOR', 'VISUAL', + // CI variables + 'CI', 'GITHUB_ACTIONS', 'GITHUB_REF', 'GITHUB_HEAD_REF', 'GITHUB_BASE_REF', 'GITHUB_EVENT_NAME', 'GITHUB_SHA', +]); + +function getSafeEnv() { + const safeEnv = {}; + for (const key in process.env) { + if (ALLOWED_ENV_KEYS.has(key) || + key.startsWith('npm_') || + key.startsWith('NOSTR_') || + key.startsWith('TORCH_') || + key.startsWith('SCHEDULER_') || + key.startsWith('AGENT_')) { + safeEnv[key] = process.env[key]; + } + } + return safeEnv; +} + +/** + * Spawns a child process with a sanitized environment by default. + * @param {string} command + * @param {string[]} args + * @param {object} options + * @param {object} [options.env] - Additional environment variables to merge. + * @param {boolean} [options.inheritProcessEnv=false] - If true, use full process.env. If false (default), use sanitized env. + */ async function runCommand(command, args = [], options = {}) { + const baseEnv = options.inheritProcessEnv ? process.env : getSafeEnv(); return new Promise((resolve) => { const child = spawn(command, args, { cwd: process.cwd(), - env: { ...process.env, ...(options.env || {}) }, + env: { ...baseEnv, ...(options.env || {}) }, stdio: ['ignore', 'pipe', 'pipe'], }); diff --git a/src/cmd-check.mjs b/src/cmd-check.mjs index a0d16f4..6d028ee 100644 --- a/src/cmd-check.mjs +++ b/src/cmd-check.mjs @@ -29,7 +29,7 @@ export async function cmdCheck(cadence, deps = {}) { console.error(`Paused agents: ${pausedAgents.join(', ')}`); } - const locks = await queryLocksFn(relays, cadence, dateStr, namespace); + const locks = await queryLocks(relays, cadence, dateStr, namespace); const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))]; const roster = await getRosterFn(cadence); diff --git a/src/constants.mjs b/src/constants.mjs index 0c0a7ec..8e5a653 100644 --- a/src/constants.mjs +++ b/src/constants.mjs @@ -11,6 +11,14 @@ export const DEFAULT_QUERY_TIMEOUT_MS = 30_000; export const DEFAULT_PUBLISH_TIMEOUT_MS = 15_000; export const DEFAULT_MIN_SUCCESSFUL_PUBLISHES = 1; export const DEFAULT_MIN_ACTIVE_RELAY_POOL = 1; +export const DEFAULT_RETRY_ATTEMPTS = 4; +export const DEFAULT_RETRY_BASE_DELAY_MS = 500; +export const DEFAULT_RETRY_CAP_DELAY_MS = 8000; +export const DEFAULT_ROLLING_WINDOW_SIZE = 25; +export const DEFAULT_FAILURE_THRESHOLD = 3; +export const DEFAULT_QUARANTINE_COOLDOWN_MS = 30_000; +export const DEFAULT_MAX_QUARANTINE_COOLDOWN_MS = 300_000; +export const DEFAULT_SNAPSHOT_INTERVAL_MS = 60_000; export const VALID_CADENCES = new Set(['daily', 'weekly']); export const KIND_APP_DATA = 30078; diff --git a/src/context/CONTEXT_2026-02-17T19-41-31Z.md b/src/context/CONTEXT_2026-02-17T19-41-31Z.md new file mode 100644 index 0000000..46d9ee8 --- /dev/null +++ b/src/context/CONTEXT_2026-02-17T19-41-31Z.md @@ -0,0 +1,10 @@ +# Context for Constants Refactor Run + +**Agent:** const-refactor-agent +**Date:** 2026-02-17T19-41-31Z +**Baseline:** +- `js/` directory does not exist. Scanning `src/` instead. +- `AGENTS.md` and `CLAUDE.md` not found. +- Target branch: default + +**Goal:** Find duplicated numeric constants in `src/` and extract them to `src/constants.mjs`. diff --git a/src/dashboard.mjs b/src/dashboard.mjs index bf9eea2..740357a 100644 --- a/src/dashboard.mjs +++ b/src/dashboard.mjs @@ -136,6 +136,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0. path.join(packageRoot, 'dashboard'), path.join(packageRoot, 'assets'), path.join(packageRoot, 'src', 'docs'), + path.join(packageRoot, 'src', 'constants.mjs'), path.join(packageRoot, 'torch-config.json') ]; diff --git a/src/decisions/DECISIONS_2026-02-17T19-43-02Z.md b/src/decisions/DECISIONS_2026-02-17T19-43-02Z.md new file mode 100644 index 0000000..be96fc8 --- /dev/null +++ b/src/decisions/DECISIONS_2026-02-17T19-43-02Z.md @@ -0,0 +1,3 @@ +- Extracted retry/health constants from `src/lock-ops.mjs` to `src/constants.mjs`. + - Constants: `DEFAULT_RETRY_ATTEMPTS`, `DEFAULT_RETRY_BASE_DELAY_MS`, `DEFAULT_RETRY_CAP_DELAY_MS`, `DEFAULT_ROLLING_WINDOW_SIZE`, `DEFAULT_FAILURE_THRESHOLD`, `DEFAULT_QUARANTINE_COOLDOWN_MS`, `DEFAULT_MAX_QUARANTINE_COOLDOWN_MS`, `DEFAULT_SNAPSHOT_INTERVAL_MS`. + - Reason: These values were hardcoded in multiple places in `src/lock-ops.mjs` and represent system-wide defaults. diff --git a/src/lock-ops.mjs b/src/lock-ops.mjs index 835a36e..eb1f64c 100644 --- a/src/lock-ops.mjs +++ b/src/lock-ops.mjs @@ -1,3 +1,4 @@ +import { randomInt, randomUUID } from 'node:crypto'; import { SimplePool } from 'nostr-tools/pool'; import { getQueryTimeoutMs, @@ -6,7 +7,17 @@ import { getRelayFallbacks, getMinActiveRelayPool, } from './torch-config.mjs'; -import { KIND_APP_DATA } from './constants.mjs'; +import { + KIND_APP_DATA, + DEFAULT_RETRY_ATTEMPTS, + DEFAULT_RETRY_BASE_DELAY_MS, + DEFAULT_RETRY_CAP_DELAY_MS, + DEFAULT_ROLLING_WINDOW_SIZE, + DEFAULT_FAILURE_THRESHOLD, + DEFAULT_QUARANTINE_COOLDOWN_MS, + DEFAULT_MAX_QUARANTINE_COOLDOWN_MS, + DEFAULT_SNAPSHOT_INTERVAL_MS, +} from './constants.mjs'; function nowUnix() { return Math.floor(Date.now() / 1000); @@ -129,7 +140,13 @@ function isTransientPublishCategory(category) { ].includes(category); } -function calculateBackoffDelayMs(attemptNumber, baseMs, capMs, randomFn = Math.random) { +const MAX_RANDOM = 281474976710655; // 2**48 - 1 + +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); } @@ -290,11 +307,11 @@ export const defaultHealthManager = new RelayHealthManager(); function buildRelayHealthConfig(deps) { return { - rollingWindowSize: deps.rollingWindowSize ?? 25, - failureThreshold: deps.failureThreshold ?? 3, - quarantineCooldownMs: deps.quarantineCooldownMs ?? 30_000, - maxQuarantineCooldownMs: deps.maxQuarantineCooldownMs ?? 5 * 60_000, - snapshotIntervalMs: deps.snapshotIntervalMs ?? 60_000, + rollingWindowSize: deps.rollingWindowSize ?? DEFAULT_ROLLING_WINDOW_SIZE, + failureThreshold: deps.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD, + quarantineCooldownMs: deps.quarantineCooldownMs ?? DEFAULT_QUARANTINE_COOLDOWN_MS, + maxQuarantineCooldownMs: deps.maxQuarantineCooldownMs ?? DEFAULT_MAX_QUARANTINE_COOLDOWN_MS, + snapshotIntervalMs: deps.snapshotIntervalMs ?? DEFAULT_SNAPSHOT_INTERVAL_MS, minActiveRelayPool: Math.max(1, deps.minActiveRelayPool), }; } @@ -408,12 +425,31 @@ async function publishToRelays(pool, relays, event, publishTimeoutMs, phase) { }); } -class LockPublisher { - constructor(relays, event, deps) { +export class LockPublisher { + constructor(relays, event, deps = {}) { this.relays = relays; this.event = event; this.deps = deps; + this.pool = null; + this.healthConfig = null; + this.allRelays = []; + this.fallbackRelays = []; + this.publishTimeoutMs = 0; + this.minSuccesses = 0; + this.minActiveRelayPool = 0; + this.maxAttempts = 0; + this.retryBaseDelayMs = 0; + this.retryCapDelayMs = 0; + this.sleepFn = null; + this.randomFn = null; + this.telemetryLogger = null; + this.healthLogger = null; + this.healthManager = null; + this.correlationId = null; + this.attemptId = null; + } + async publish() { const { poolFactory = () => new SimplePool(), getPublishTimeoutMsFn = getPublishTimeoutMs, @@ -448,14 +484,17 @@ class LockPublisher { this.randomFn = randomFn; this.telemetryLogger = telemetryLogger; this.healthLogger = healthLogger; + this.healthManager = healthManager; + this.correlationId = diagnostics.correlationId || randomUUID(); + this.attemptId = diagnostics.attemptId || randomUUID(); - this.correlationId = diagnostics.correlationId || process.env.SCHEDULER_LOCK_CORRELATION_ID || 'none'; - this.attemptId = diagnostics.attemptId || process.env.SCHEDULER_LOCK_ATTEMPT_ID || '1'; + this.healthConfig = buildRelayHealthConfig({ + ...this.deps, + minActiveRelayPool: this.minActiveRelayPool + }); - this.allRelays = mergeRelayList(relays, this.fallbackRelays); - } + this.allRelays = mergeRelayList(this.relays, this.fallbackRelays); - async publish() { try { this.healthManager.maybeLogSnapshot(this.allRelays, this.healthConfig, this.healthLogger, 'publish:periodic'); let lastAttemptState = null; @@ -663,3 +702,5 @@ export async function publishLock(relays, event, deps = {}) { export function _resetRelayHealthState() { defaultHealthManager.reset(); } + +export const _secureRandom = secureRandom; diff --git a/src/ops.mjs b/src/ops.mjs index d8d4b38..880fb1f 100644 --- a/src/ops.mjs +++ b/src/ops.mjs @@ -152,36 +152,33 @@ async function interactiveInit(cwd) { } } -export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers = null) { - // Use interactive mode to get configuration unless mock answers are provided - let config; +async function resolveConfiguration(cwd, mockAnswers) { if (mockAnswers) { - config = mockAnswers; - } else { - config = await interactiveInit(cwd); + return mockAnswers; } - const { installDir, namespace, relays } = config; + return await interactiveInit(cwd); +} - const paths = getPaths(cwd, installDir); - console.log(`\nInitializing torch in ${paths.torchDir}...`); - - // Check if directory exists and is not empty (unless force is used or we are installing to .) +function ensureInstallDirectory(paths, force, installDir) { if (fs.existsSync(paths.torchDir) && !force) { const entries = fs.readdirSync(paths.torchDir); if (entries.length > 0 && installDir !== '.') { throw new Error(`Directory ${paths.torchDir} already exists and is not empty. Use --force to overwrite.`); } } - ensureDir(paths.torchDir); ensureDir(paths.promptsDir); +} +function installAppAssets(torchDir, installDir) { // 1. Copy App Directories - syncAppDirectories(paths.torchDir, 'Copied'); + syncAppDirectories(torchDir, 'Copied'); // 2. Copy App Files - syncAppFiles(paths.torchDir, installDir, 'Copied'); + syncAppFiles(torchDir, installDir, 'Copied'); +} +function installTorchAssets(paths, installDir) { // 3. Copy Roster const srcRoster = path.join(SRC_PROMPTS_DIR, 'roster.json'); if (fs.existsSync(srcRoster)) { @@ -215,7 +212,9 @@ export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers = console.log(`Created ${files.length} files in ${path.relative(paths.root, destDir)}/`); } } +} +function configureTorch(cwd, paths, installDir, namespace, relays) { // 6. Create/Update torch-config.json const configPath = path.join(paths.root, 'torch-config.json'); @@ -268,12 +267,29 @@ export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers = fs.writeFileSync(configPath, JSON.stringify(configData, null, 2), 'utf8'); console.log(`Saved configuration to ${path.relative(cwd, configPath)}`); +} +function injectHostScriptsIfNeeded(paths, installDir) { // 7. Inject Scripts into Host Package.json // If we are NOT installing to '.', the host package.json is in paths.root if (installDir !== '.') { injectScriptsIntoHost(paths.root, installDir); } +} + +export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers = null) { + const config = await resolveConfiguration(cwd, mockAnswers); + const { installDir, namespace, relays } = config; + + const paths = getPaths(cwd, installDir); + console.log(`\nInitializing torch in ${paths.torchDir}...`); + + ensureInstallDirectory(paths, force, installDir); + + installAppAssets(paths.torchDir, installDir); + installTorchAssets(paths, installDir); + configureTorch(cwd, paths, installDir, namespace, relays); + injectHostScriptsIfNeeded(paths, installDir); console.log('\nInitialization complete.'); console.log('You can now customize the files in ' + path.relative(cwd, paths.torchDir) + '/'); diff --git a/src/test_logs/TEST_LOG_2026-02-17T19-43-26Z.md b/src/test_logs/TEST_LOG_2026-02-17T19-43-26Z.md new file mode 100644 index 0000000..f6e63b9 --- /dev/null +++ b/src/test_logs/TEST_LOG_2026-02-17T19-43-26Z.md @@ -0,0 +1,227 @@ + +> torch-lock@0.1.0 test:unit:lock-backend +> node --test test/lock-ops.test.mjs test/lock-ops-publish-retry.test.mjs + +TAP version 13 +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# Published to 1/1 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# Published to 1/1 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# (node:3973) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time +# (Use `node --trace-warnings ...` to show where the warning was created) +# Subtest: publishLock retry behavior + # Subtest: succeeds immediately on first attempt without retries + ok 1 - succeeds immediately on first attempt without retries + --- + duration_ms: 6.654526 + type: 'test' + ... + # Subtest: retries a transient failure then succeeds within retry budget with bounded jitter + ok 2 - retries a transient failure then succeeds within retry budget with bounded jitter + --- + duration_ms: 4.073858 + type: 'test' + ... + # Subtest: returns terminal relay_publish_quorum_failure after exhausting retry budget + ok 3 - returns terminal relay_publish_quorum_failure after exhausting retry budget + --- + duration_ms: 5.742571 + type: 'test' + ... + # Subtest: short-circuits non-retryable failures without extra attempts + ok 4 - short-circuits non-retryable failures without extra attempts + --- + duration_ms: 2.299061 + type: 'test' + ... + 1..4 +ok 1 - publishLock retry behavior + --- + duration_ms: 20.363862 + type: 'suite' + ... +# Subtest: lock-ops + # Subtest: parseLockEvent + # Subtest: parses a valid lock event correctly + ok 1 - parses a valid lock event correctly + --- + duration_ms: 1.874948 + type: 'test' + ... + 1..1 + ok 1 - parseLockEvent + --- + duration_ms: 2.822323 + type: 'suite' + ... +# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... +# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... +# Published to 2/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://primary-down)... +# [publish:primary] Publishing to 1 relays (wss://primary-down)... +# [publish:primary] Publishing to 1 relays (wss://primary-down)... +# [publish:primary] Publishing to 1 relays (wss://primary-bad)... +# [publish:primary] Publishing to 5 relays (relay-timeout, wss://dns, wss://tcp, wss://tls, wss://ws)... +# [publish:primary] Publishing to 2 relays (wss://relay-bad, wss://relay-ok)... +# {"event":"lock_publish_quorum_met","correlationId":"7d70b395-2145-4470-9f33-9b602a34792d","attemptId":"c0035955-5f07-4d6b-a450-e07811f00e2f","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":1}],"totalElapsedMs":1} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://primary-bad)... +# [publish:fallback] Publishing to 1 relays (wss://fallback-ok)... +# {"event":"lock_publish_quorum_met","correlationId":"239da9db-92a7-44f8-b694-52a4a6ddf437","attemptId":"12267c26-1926-4f5b-835b-f92682e5ffc4","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://bad, wss://healthy)... +# {"event":"lock_publish_quorum_met","correlationId":"54df96c1-356d-40a2-a300-0a5fce94d1f9","attemptId":"72e3fd52-0c75-4f3f-a43f-241ed8d973e1","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://healthy, wss://bad)... +# {"event":"lock_publish_quorum_met","correlationId":"35e05397-38ab-4fd5-b979-9f731dc5765a","attemptId":"c6410080-1244-4f4b-9139-dbb85187b441","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://healthy, wss://bad)... +# {"event":"lock_publish_quorum_met","correlationId":"e6f1916c-879a-4ed2-953e-7a519c11d857","attemptId":"1aa9c36c-8755-45ca-9fe6-6a89f883c152","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://healthy)... +# {"event":"lock_publish_quorum_met","correlationId":"56c8195a-74eb-4b72-a495-3e1efcb4a3f0","attemptId":"d2e0b655-7115-4f61-91fc-139dc5ec9db4","publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/1 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... +# [publish:fallback] Publishing to 1 relays (wss://fallback-healthy)... +# {"event":"lock_publish_quorum_met","correlationId":"94251c93-ebf1-481d-8fb5-c8a73bdf0a20","attemptId":"533fb740-98ec-4358-9d32-78c8b0ac72a4","publishAttempt":1,"successCount":1,"relayAttemptedCount":3,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":3,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/3 relays (required=1, timeout=200ms) + # Subtest: queryLocks + # Subtest: falls back to fallback relays when primary query fails + ok 1 - falls back to fallback relays when primary query fails + --- + duration_ms: 27.235965 + type: 'test' + ... + 1..1 + ok 2 - queryLocks + --- + duration_ms: 27.514651 + type: 'suite' + ... + # Subtest: publishLock + # Subtest: retries transient failures and succeeds on a later attempt + not ok 1 - retries transient failures and succeeds on a later attempt + --- + duration_ms: 6.53189 + type: 'test' + location: '/app/test/lock-ops.test.mjs:78:5' + failureType: 'testCodeFailure' + error: |- + Expected values to be strictly deep-equal: + + actual - expected + + { + + attemptId: '52c2baa6-8ff8-4f42-b6b3-ead606942315', + + correlationId: '6428aa2b-60f0-40a5-b385-525fc25c46b0', + - attemptId: 'attempt-1', + - correlationId: 'corr-1', + elapsedMs: 1, + errorCategory: 'connection_reset', + event: 'lock_publish_retry', + nextDelayMs: 250, + publishAttempt: 1, + + code: 'ERR_ASSERTION' + name: 'AssertionError' + expected: + event: 'lock_publish_retry' + correlationId: 'corr-1' + attemptId: 'attempt-1' + publishAttempt: 1 + relayUrl: 'wss://primary-a' + errorCategory: 'connection_reset' + elapsedMs: 1 + nextDelayMs: 250 + actual: + event: 'lock_publish_retry' + correlationId: '6428aa2b-60f0-40a5-b385-525fc25c46b0' + attemptId: '52c2baa6-8ff8-4f42-b6b3-ead606942315' + publishAttempt: 1 + relayUrl: 'wss://primary-a' + errorCategory: 'connection_reset' + elapsedMs: 1 + nextDelayMs: 250 + operator: 'deepStrictEqual' + stack: |- + TestContext. (file:///app/test/lock-ops.test.mjs:124:14) + async Test.run (node:internal/test_runner/test:1054:7) + async Promise.all (index 0) + async Suite.run (node:internal/test_runner/test:1442:7) + async Suite.processPendingSubtests (node:internal/test_runner/test:744:7) + ... + # Subtest: fails after retry budget is exhausted for persistent transient failures + ok 2 - fails after retry budget is exhausted for persistent transient failures + --- + duration_ms: 2.875565 + type: 'test' + ... + # Subtest: fails without retry for persistent validation failures + ok 3 - fails without retry for persistent validation failures + --- + duration_ms: 1.360655 + type: 'test' + ... + # Subtest: includes per-relay reason categories and retry metadata in failure diagnostics + ok 4 - includes per-relay reason categories and retry metadata in failure diagnostics + --- + duration_ms: 2.145677 + type: 'test' + ... + # Subtest: returns success when mixed relay outcomes still satisfy quorum + ok 5 - returns success when mixed relay outcomes still satisfy quorum + --- + duration_ms: 1.586006 + type: 'test' + ... + # Subtest: uses fallback relays to satisfy min success quorum + ok 6 - uses fallback relays to satisfy min success quorum + --- + duration_ms: 1.059862 + type: 'test' + ... + # Subtest: quarantines repeatedly failing relays and still reaches quorum with one healthy relay + ok 7 - quarantines repeatedly failing relays and still reaches quorum with one healthy relay + --- + duration_ms: 2.500909 + type: 'test' + ... + # Subtest: uses fallback and min active pool to reintroduce quarantined relay when needed + ok 8 - uses fallback and min active pool to reintroduce quarantined relay when needed + --- + duration_ms: 1.073546 + type: 'test' + ... + 1..8 + not ok 3 - publishLock + --- + duration_ms: 19.967883 + type: 'suite' + location: '/app/test/lock-ops.test.mjs:77:3' + failureType: 'subtestsFailed' + error: '1 subtest failed' + code: 'ERR_TEST_FAILURE' + ... + 1..3 +not ok 2 - lock-ops + --- + duration_ms: 52.981447 + type: 'suite' + location: '/app/test/lock-ops.test.mjs:10:1' + failureType: 'subtestsFailed' + error: '1 subtest failed' + code: 'ERR_TEST_FAILURE' + ... +1..2 +# tests 14 +# suites 5 +# pass 13 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 196.37029 diff --git a/src/test_logs/TEST_LOG_2026-02-17T19-47-44Z.md b/src/test_logs/TEST_LOG_2026-02-17T19-47-44Z.md new file mode 100644 index 0000000..f7d19d7 --- /dev/null +++ b/src/test_logs/TEST_LOG_2026-02-17T19-47-44Z.md @@ -0,0 +1,175 @@ + +> torch-lock@0.1.0 test:unit:lock-backend +> node --test test/lock-ops.test.mjs test/lock-ops-publish-retry.test.mjs + +TAP version 13 +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# Published to 1/1 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# Published to 1/1 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# [publish:primary] Publishing to 1 relays (wss://relay-a)... +# (node:5876) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time +# (Use `node --trace-warnings ...` to show where the warning was created) +# Subtest: publishLock retry behavior + # Subtest: succeeds immediately on first attempt without retries + ok 1 - succeeds immediately on first attempt without retries + --- + duration_ms: 6.413745 + type: 'test' + ... + # Subtest: retries a transient failure then succeeds within retry budget with bounded jitter + ok 2 - retries a transient failure then succeeds within retry budget with bounded jitter + --- + duration_ms: 3.062285 + type: 'test' + ... + # Subtest: returns terminal relay_publish_quorum_failure after exhausting retry budget + ok 3 - returns terminal relay_publish_quorum_failure after exhausting retry budget + --- + duration_ms: 4.611267 + type: 'test' + ... + # Subtest: short-circuits non-retryable failures without extra attempts + ok 4 - short-circuits non-retryable failures without extra attempts + --- + duration_ms: 2.07494 + type: 'test' + ... + 1..4 +ok 1 - publishLock retry behavior + --- + duration_ms: 17.852297 + type: 'suite' + ... +# Subtest: lock-ops + # Subtest: parseLockEvent + # Subtest: parses a valid lock event correctly + ok 1 - parses a valid lock event correctly + --- + duration_ms: 1.583449 + type: 'test' + ... + 1..1 + ok 1 - parseLockEvent + --- + duration_ms: 2.601962 + type: 'suite' + ... +# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... +# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... +# Published to 2/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://primary-down)... +# [publish:primary] Publishing to 1 relays (wss://primary-down)... +# [publish:primary] Publishing to 1 relays (wss://primary-down)... +# [publish:primary] Publishing to 1 relays (wss://primary-bad)... +# [publish:primary] Publishing to 5 relays (relay-timeout, wss://dns, wss://tcp, wss://tls, wss://ws)... +# [publish:primary] Publishing to 2 relays (wss://relay-bad, wss://relay-ok)... +# {"event":"lock_publish_quorum_met","correlationId":"1be06dfa-a31e-42b0-b789-327fd9e11131","attemptId":"10405dd5-2f84-4e23-9a5a-696abb34c1bc","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":1}],"totalElapsedMs":1} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://primary-bad)... +# [publish:fallback] Publishing to 1 relays (wss://fallback-ok)... +# {"event":"lock_publish_quorum_met","correlationId":"becb5a52-cef6-4bc6-84b1-ca4d14d63e04","attemptId":"25dca8be-61ee-42b8-a764-7ebdc46f234b","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":1}],"totalElapsedMs":1} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://bad, wss://healthy)... +# {"event":"lock_publish_quorum_met","correlationId":"67ecc056-eaa3-40c2-9042-15e033b7a252","attemptId":"07181123-946d-4823-bc4a-c79d13a84920","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":1}],"totalElapsedMs":1} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://healthy, wss://bad)... +# {"event":"lock_publish_quorum_met","correlationId":"6075ee96-8def-4111-84e2-d7b395950a84","attemptId":"bb03ae8b-ff47-41e2-ba91-b3031d38c497","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://healthy, wss://bad)... +# {"event":"lock_publish_quorum_met","correlationId":"38f8db94-b61b-4b02-9a33-05fd8da73e5e","attemptId":"e645bbac-64ec-4f61-a865-a91d0adc739c","publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":2,"elapsedMs":1}],"totalElapsedMs":1} +# Published to 1/2 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 1 relays (wss://healthy)... +# {"event":"lock_publish_quorum_met","correlationId":"4c6a0906-bd5b-431b-9af1-1d4a87aa59d3","attemptId":"13836194-8337-49af-8f81-8038a7101d4b","publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":1,"elapsedMs":0}],"totalElapsedMs":0} +# Published to 1/1 relays (required=1, timeout=200ms) +# [publish:primary] Publishing to 2 relays (wss://primary-a, wss://primary-b)... +# [publish:fallback] Publishing to 1 relays (wss://fallback-healthy)... +# {"event":"lock_publish_quorum_met","correlationId":"2e7e8c9f-5b93-4cd3-8324-a63b981e9e4c","attemptId":"d53e657e-431a-4203-a017-256ba03a05db","publishAttempt":1,"successCount":1,"relayAttemptedCount":3,"requiredSuccesses":1,"timeoutMs":200,"retryTimeline":[{"publishAttempt":1,"successCount":1,"relayAttemptedCount":3,"elapsedMs":1}],"totalElapsedMs":2} +# Published to 1/3 relays (required=1, timeout=200ms) + # Subtest: queryLocks + # Subtest: falls back to fallback relays when primary query fails + ok 1 - falls back to fallback relays when primary query fails + --- + duration_ms: 27.905124 + type: 'test' + ... + 1..1 + ok 2 - queryLocks + --- + duration_ms: 28.20218 + type: 'suite' + ... + # Subtest: publishLock + # Subtest: retries transient failures and succeeds on a later attempt + ok 1 - retries transient failures and succeeds on a later attempt + --- + duration_ms: 4.944259 + type: 'test' + ... + # Subtest: fails after retry budget is exhausted for persistent transient failures + ok 2 - fails after retry budget is exhausted for persistent transient failures + --- + duration_ms: 3.551748 + type: 'test' + ... + # Subtest: fails without retry for persistent validation failures + ok 3 - fails without retry for persistent validation failures + --- + duration_ms: 1.406267 + type: 'test' + ... + # Subtest: includes per-relay reason categories and retry metadata in failure diagnostics + ok 4 - includes per-relay reason categories and retry metadata in failure diagnostics + --- + duration_ms: 2.101245 + type: 'test' + ... + # Subtest: returns success when mixed relay outcomes still satisfy quorum + ok 5 - returns success when mixed relay outcomes still satisfy quorum + --- + duration_ms: 1.547853 + type: 'test' + ... + # Subtest: uses fallback relays to satisfy min success quorum + ok 6 - uses fallback relays to satisfy min success quorum + --- + duration_ms: 1.380374 + type: 'test' + ... + # Subtest: quarantines repeatedly failing relays and still reaches quorum with one healthy relay + ok 7 - quarantines repeatedly failing relays and still reaches quorum with one healthy relay + --- + duration_ms: 3.040514 + type: 'test' + ... + # Subtest: uses fallback and min active pool to reintroduce quarantined relay when needed + ok 8 - uses fallback and min active pool to reintroduce quarantined relay when needed + --- + duration_ms: 2.862114 + type: 'test' + ... + 1..8 + ok 3 - publishLock + --- + duration_ms: 21.884946 + type: 'suite' + ... + 1..3 +ok 2 - lock-ops + --- + duration_ms: 53.519877 + type: 'suite' + ... +1..2 +# tests 14 +# suites 5 +# pass 14 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 212.244831 diff --git a/src/todo/TODO_2026-02-17T19-43-02Z.md b/src/todo/TODO_2026-02-17T19-43-02Z.md new file mode 100644 index 0000000..96de603 --- /dev/null +++ b/src/todo/TODO_2026-02-17T19-43-02Z.md @@ -0,0 +1 @@ +- [x] Extract lock-ops constants to src/constants.mjs diff --git a/task-logs/daily/2026-02-17T19-45-47Z__const-refactor-agent__completed.md b/task-logs/daily/2026-02-17T19-45-47Z__const-refactor-agent__completed.md new file mode 100644 index 0000000..f129e8c --- /dev/null +++ b/task-logs/daily/2026-02-17T19-45-47Z__const-refactor-agent__completed.md @@ -0,0 +1,15 @@ +--- +cadence: daily +agent: const-refactor-agent +status: completed +reason: 'Scheduler cycle completed successfully' +created_at: 2026-02-17T19-48-02Z +timestamp: 2026-02-17T19-48-02Z +platform: linux +--- + +# Scheduler completed + +- reason: Scheduler cycle completed successfully +- platform: linux +- details: Extracted lock backend constants to src/constants.mjs, refactored lock-ops.mjs, fixed cmd-check.mjs bug. diff --git a/task-logs/weekly/2026-02-17T19-44-07Z__frontend-console-debug-agent__completed.md b/task-logs/weekly/2026-02-17T19-44-07Z__frontend-console-debug-agent__completed.md new file mode 100644 index 0000000..ea4dcbe --- /dev/null +++ b/task-logs/weekly/2026-02-17T19-44-07Z__frontend-console-debug-agent__completed.md @@ -0,0 +1,34 @@ +--- +agent: frontend-console-debug-agent +status: completed +date: 2026-02-17 +--- + +# Weekly Frontend Console Debug + +## Summary +Detected and resolved a `403 Forbidden` error preventing the dashboard from loading `src/constants.mjs`. + +## Actions +1. **Fixed Infrastructure**: + - Refactored `src/lock-ops.mjs` to fix a critical syntax error in the `LockPublisher` class. + - Verified the fix by successfully running `npm run lock:check:weekly`. + +2. **Frontend Debugging**: + - Installed `@playwright/test` to capture console errors. + - Created `scripts/agent/debug_frontend.mjs` which launches a headless browser to inspect the dashboard. + - Identified a blocking `403 Forbidden` error when the dashboard attempted to load `src/constants.mjs`. + +3. **Remediation**: + - Updated `src/dashboard.mjs` to explicitly allow serving `src/constants.mjs` by adding it to the `allowedPaths` whitelist. This allows the frontend to access shared constants while maintaining security restrictions on other source files. + - Fixed a typo in `src/cmd-check.mjs` where `queryLocksFn` was referenced instead of `queryLocks`. + +4. **Verification**: + - Re-ran `scripts/agent/debug_frontend.mjs` and confirmed the dashboard loads with zero console errors. + - Ran `npm run lint` (passed with warnings). + - Ran `npm run build` (successful). + - Ran `npm run test:unit:lock-backend` (all tests passed). + +## Artifacts +- `scripts/agent/debug_frontend.mjs` +- `package.json` (added `@playwright/test`) diff --git a/test/secure-random.test.mjs b/test/secure-random.test.mjs new file mode 100644 index 0000000..af64c3b --- /dev/null +++ b/test/secure-random.test.mjs @@ -0,0 +1,21 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { _secureRandom } from '../src/lock-ops.mjs'; + +describe('secureRandom', () => { + it('returns values in [0, 1)', () => { + for (let i = 0; i < 1000; i++) { + const val = _secureRandom(); + assert.ok(val >= 0, `Value ${val} should be >= 0`); + assert.ok(val < 1, `Value ${val} should be < 1`); + } + }); + + it('returns varied values', () => { + const values = new Set(); + for (let i = 0; i < 100; i++) { + values.add(_secureRandom()); + } + assert.ok(values.size > 90, 'Values should be reasonably unique'); + }); +});