[object Object]

← back to Tk 10821 Enrichment Health Classifier

add evidence-based enrichment health classifier

c572205eda60bacb86b33c5c9c86fd5c54e749b6 · 2026-08-30 12:57:59 -0700 · Steve Abrams

Files touched

Diff

commit c572205eda60bacb86b33c5c9c86fd5c54e749b6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 30 12:57:59 2026 -0700

    add evidence-based enrichment health classifier
---
 .gitignore                       |  8 +++++++
 README.md                        | 18 +++++++++++++++
 classify.mjs                     | 49 ++++++++++++++++++++++++++++++++++++++++
 fixtures/current-2026-08-30.json | 17 ++++++++++++++
 test.mjs                         | 25 ++++++++++++++++++++
 verification/e2e-proof.json      | 34 ++++++++++++++++++++++++++++
 6 files changed, 151 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378c83f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.env*
+node_modules/
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bd1ce03
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# TK-10821 enrichment-health classifier
+
+This pure local guard prevents the Phase-3 monitor from equating a flat or
+growing **net pending count** with a dead enrichment cron. New work can enter
+while old work is processed, so backlog delta alone is not liveness.
+
+The classifier requires direct signals: time since `phase3_ai_at` advanced,
+completed rows in 24 hours, cron completion/exit state, and a sampled run's
+attempted/updated/no-data counts. It never calls an API or database.
+
+Current authoritative snapshot (2026-08-30): the cron is alive, with the last
+advance 8.81 hours ago and 543 completions in seven days. Throughput is low; a
+Wolf Gordon sample attempted 42 products and returned no AI data for all 42.
+That is `ALIVE_UPSTREAM_CONSTRAINED`, not `STALLED`.
+
+The three vendors named in the original ticket are excluded from this pipeline
+by rule and prior read-only evidence shows they are materially enriched. No paid
+drain should be started from this ticket's old premise.
diff --git a/classify.mjs b/classify.mjs
new file mode 100644
index 0000000..aabbc2d
--- /dev/null
+++ b/classify.mjs
@@ -0,0 +1,49 @@
+#!/usr/bin/env node
+// TK-10821: pure, local enrichment-health classifier.
+// It consumes an evidence snapshot and cannot call APIs, databases, or schedules.
+import fs from 'node:fs';
+
+export function classify(snapshot) {
+  const required = ['hours_since_last_advance', 'processed_24h', 'cron_last_exit', 'cron_completed_today', 'actionable_pending'];
+  const missing = required.filter(key => snapshot[key] === null || snapshot[key] === undefined);
+  if (missing.length) return { verdict: 'DEGRADED_OBSERVABILITY', stalled: null, reasons: [`missing: ${missing.join(', ')}`] };
+
+  const fresh = Number(snapshot.hours_since_last_advance) <= 48;
+  const processed = Number(snapshot.processed_24h) > 0;
+  const cronHealthy = snapshot.cron_last_exit === 0 && snapshot.cron_completed_today === true;
+  const stalled = !fresh && !processed && !cronHealthy;
+  const reasons = [
+    `last phase3 advance ${snapshot.hours_since_last_advance}h ago`,
+    `${snapshot.processed_24h} completed in 24h`,
+    `cron last_exit=${snapshot.cron_last_exit}, completed_today=${snapshot.cron_completed_today}`
+  ];
+  if (stalled) return { verdict: 'STALLED', stalled: true, reasons };
+
+  const attempted = Number(snapshot.sample_attempted || 0);
+  const updated = Number(snapshot.sample_updated || 0);
+  const noData = Number(snapshot.sample_skipped_no_data || 0);
+  if (attempted > 0 && updated / attempted < 0.05 && noData / attempted >= 0.8) {
+    return {
+      verdict: 'ALIVE_UPSTREAM_CONSTRAINED', stalled: false,
+      reasons: [...reasons, `sample run updated ${updated}/${attempted}; ${noData}/${attempted} returned no AI data`]
+    };
+  }
+
+  const previous = Number(snapshot.previous_actionable_pending);
+  const current = Number(snapshot.actionable_pending);
+  if (Number.isFinite(previous) && current >= previous) {
+    return {
+      verdict: 'ALIVE_NET_BACKLOG_FLAT_OR_GROWING', stalled: false,
+      reasons: [...reasons, `net pending ${previous}->${current}; net delta is not a liveness measurement`]
+    };
+  }
+  return { verdict: 'ALIVE_DRAINING', stalled: false, reasons };
+}
+
+if (process.argv[1] && new URL(import.meta.url).pathname === process.argv[1]) {
+  const input = process.argv[2];
+  if (!input) throw new Error('usage: node classify.mjs SNAPSHOT.json');
+  const result = classify(JSON.parse(fs.readFileSync(input, 'utf8')));
+  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
+  process.exitCode = result.verdict === 'STALLED' || result.verdict === 'DEGRADED_OBSERVABILITY' ? 1 : 0;
+}
diff --git a/fixtures/current-2026-08-30.json b/fixtures/current-2026-08-30.json
new file mode 100644
index 0000000..426ef94
--- /dev/null
+++ b/fixtures/current-2026-08-30.json
@@ -0,0 +1,17 @@
+{
+  "source": "Kamatera read-only psql + full-monte phase3 log + local launchctl",
+  "captured_at": "2026-08-30T19:55:18Z",
+  "hours_since_last_advance": 8.81,
+  "processed_24h": 1,
+  "processed_7d": 543,
+  "cron_last_exit": 0,
+  "cron_completed_today": true,
+  "actionable_pending": 4236,
+  "previous_actionable_pending": 4210,
+  "raw_pending": 12219,
+  "sample_vendor": "wolf_gordon",
+  "sample_attempted": 42,
+  "sample_updated": 0,
+  "sample_skipped_no_data": 42,
+  "named_vendor_status": "excluded-by-rule; prior ticket evidence shows materially enriched, not dark"
+}
diff --git a/test.mjs b/test.mjs
new file mode 100644
index 0000000..3dd92e3
--- /dev/null
+++ b/test.mjs
@@ -0,0 +1,25 @@
+#!/usr/bin/env node
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import { classify } from './classify.mjs';
+
+const current = JSON.parse(fs.readFileSync(new URL('./fixtures/current-2026-08-30.json', import.meta.url)));
+assert.deepEqual(classify(current), {
+  verdict: 'ALIVE_UPSTREAM_CONSTRAINED', stalled: false,
+  reasons: [
+    'last phase3 advance 8.81h ago', '1 completed in 24h',
+    'cron last_exit=0, completed_today=true',
+    'sample run updated 0/42; 42/42 returned no AI data'
+  ]
+});
+
+const dead = { hours_since_last_advance: 56, processed_24h: 0, cron_last_exit: 1, cron_completed_today: false, actionable_pending: 4210 };
+assert.equal(classify(dead).verdict, 'STALLED');
+
+const inflowMasksWork = { hours_since_last_advance: 2, processed_24h: 50, cron_last_exit: 0, cron_completed_today: true, actionable_pending: 4300, previous_actionable_pending: 4210 };
+assert.equal(classify(inflowMasksWork).verdict, 'ALIVE_NET_BACKLOG_FLAT_OR_GROWING');
+assert.equal(classify(inflowMasksWork).stalled, false);
+
+const missing = { hours_since_last_advance: 2 };
+assert.equal(classify(missing).verdict, 'DEGRADED_OBSERVABILITY');
+console.log('PASS: current, true-stall, inflow-masks-work, and missing-evidence classifications');
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
new file mode 100644
index 0000000..908607c
--- /dev/null
+++ b/verification/e2e-proof.json
@@ -0,0 +1,34 @@
+{
+  "ticket": "TK-10821",
+  "intent": "Prevent a flat net backlog from being misreported as a dead Phase-3 pipeline",
+  "risk_tier": "R1",
+  "environment": "pure local classifier over a captured read-only Kamatera/launchd/log snapshot",
+  "timestamp": "2026-08-30T19:55:18Z",
+  "baseline": "Monitor reported 4,236 remaining and NO drain; ticket alleged a 56-hour stall and three dark vendors",
+  "assertions": [
+    "Kamatera crontab contains Phase-3 tier jobs at 09:00, 10:00, and 11:00 UTC",
+    "max phase3_ai_at is 2026-08-30 11:06:50Z; 1 completion in 24h and 543 in 7d",
+    "today's tier-3 log completed 154 vendors successfully and failed 3",
+    "Wolf Gordon sample attempted 42, updated 0, skipped-no-data 42",
+    "local enrichment-health and phase3-monitor launchd jobs are loaded with last exit 0",
+    "current fixture classifies ALIVE_UPSTREAM_CONSTRAINED, not STALLED",
+    "a synthetic 56h/no-progress/failed-cron fixture classifies STALLED",
+    "a growing backlog with direct processing evidence remains alive",
+    "missing direct evidence fails closed as DEGRADED_OBSERVABILITY"
+  ],
+  "commands": [
+    "node --check classify.mjs",
+    "node test.mjs",
+    "node classify.mjs fixtures/current-2026-08-30.json"
+  ],
+  "side_effect_boundaries": {
+    "paid_api_calls": 0,
+    "production_batches_started": 0,
+    "database_or_shopify_writes": 0,
+    "deploys": 0,
+    "schedule_installs_or_reloads": 0,
+    "backend_kills": 0
+  },
+  "cleanup": "No external test state created",
+  "verdict": "PASS"
+}

(oldest)  ·  back to Tk 10821 Enrichment Health Classifier  ·  (newest)