[object Object]

← back to Cncp Failure Collector

tick 4: fire-and-forget classify on new rows (5min per-process rate limit, env-var disable, server single-flight)

8a795141e7e11df0983c41b205c85d635c2cd5a5 · 2026-05-11 12:05:51 -0700 · SteveStudio2

Files touched

Diff

commit 8a795141e7e11df0983c41b205c85d635c2cd5a5
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Mon May 11 12:05:51 2026 -0700

    tick 4: fire-and-forget classify on new rows (5min per-process rate limit, env-var disable, server single-flight)
---
 ecosystem.config.js |  3 ++-
 index.js            | 18 +++++++++++++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/ecosystem.config.js b/ecosystem.config.js
index 23ad2de..75c3e61 100644
--- a/ecosystem.config.js
+++ b/ecosystem.config.js
@@ -17,7 +17,8 @@ module.exports = {
     env: {
       NODE_ENV: 'production',
       CNCP_URL: 'http://127.0.0.1:3333',
-      POLL_INTERVAL_MS: '30000'
+      POLL_INTERVAL_MS: '30000',
+      CLASSIFIER_ENABLED: '1'
     }
   }]
 };
diff --git a/index.js b/index.js
index 62f5493..8f355dc 100644
--- a/index.js
+++ b/index.js
@@ -24,6 +24,9 @@ const { execSync, exec } = require('child_process');
 
 const CNCP_URL = process.env.CNCP_URL || 'http://127.0.0.1:3333';
 const POLL_INTERVAL_MS = parseInt(process.env.POLL_INTERVAL_MS, 10) || 30_000;
+const CLASSIFIER_ENABLED = (process.env.CLASSIFIER_ENABLED || '1') !== '0';
+const CLASSIFY_RATE_LIMIT_MS = 5 * 60 * 1000;
+const lastClassify = new Map(); // processName -> timestamp
 const STATE_FILE = path.join(__dirname, 'state.json');
 const ONCE = process.argv.includes('--once');
 
@@ -51,7 +54,20 @@ async function postTask(payload) {
       body: JSON.stringify(payload)
     });
     if (!r.ok) { log('POST /api/failures failed', r.status, await r.text().catch(()=>''));  return null; }
-    return await r.json();
+    const result = await r.json();
+    // Fire-and-forget classification on NEW rows only. Per-process 5-min rate limit + server-side single-flight.
+    if (CLASSIFIER_ENABLED && result.deduped === false && result.task && result.task.id) {
+      const name = payload.processName;
+      const last = lastClassify.get(name) || 0;
+      if (Date.now() - last >= CLASSIFY_RATE_LIMIT_MS) {
+        lastClassify.set(name, Date.now());
+        fetch(`${CNCP_URL}/api/failures/${result.task.id}/classify`, { method: 'POST' })
+          .then(rc => rc.json())
+          .then(d => log(`classify ${name}: ${d.classified ? d.task.severity + '/' + d.task.category : (d.busy ? 'busy' : 'failed: ' + (d.error || '?'))}`))
+          .catch(e => log(`classify ${name} threw:`, e.message));
+      }
+    }
+    return result;
   } catch (e) {
     log('POST /api/failures threw:', e.message);
     return null;

← c57181c tick 3: launchd plist-driven .err.log tailing (97 com.steve.  ·  back to Cncp Failure Collector  ·  tick 6: README pass — covers ticks 2-5b features (classifier 4292cad →