[object Object]

← back to Homesonspec

Publish a fleet-health heartbeat: this poller was invisible to the rollup

ed938c0f4cd2b7a2286fdbf680856fd79b2f2aa3 · 2026-09-13 16:36:50 -0700 · Steve

Found by a red-team pass on my own changes. fleet-health-rollup globs
~/.claude/skills/*/data/latest.json and SILENTLY SKIPS a skill with no such file
(`if (!existsSync(file)) continue`). This poller wrote state only to
ops/runtime/, so it contributed ZERO signal and was unknown to
dw-canary-meta-watchdog too: if it died, the 7am panel would say nothing was
wrong — about the one app under a live Guideline 5.6 conduct citation.

Worse, my previous commit's own comment claimed "alert_delivered lands in
latest.json for fleet-health-rollup". There was no latest.json for this skill.
I asserted an integration I never tested, in the same session spent hunting
exactly that failure. Comment corrected; the integration now actually exists and
is actually tested — the rollup picks the row up (verdict=ATTENTION, status=WARN).

- heartbeatStatus(): approved/pending -> PASS, rejected -> WARN (matching
  ios-release-train's ATTENTION), and an outcome we could NOT measure ->
  NOT_MEASURED/WARN, never PASS (TK-11431 #1). A null state means ASC was
  unreachable or the row did not parse; that is not evidence all is well.
- Published AFTER the durable state write and wrapped so a heartbeat failure can
  never cost the observation.
- Never published from a --fixture run (the seam guard learned earlier today).
- Two tests lock the mapping, incl. the negative case.

TK-11155

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPS3gnB4H5MQKmWcHKvuf6

Files touched

Diff

commit ed938c0f4cd2b7a2286fdbf680856fd79b2f2aa3
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Sep 13 16:36:50 2026 -0700

    Publish a fleet-health heartbeat: this poller was invisible to the rollup
    
    Found by a red-team pass on my own changes. fleet-health-rollup globs
    ~/.claude/skills/*/data/latest.json and SILENTLY SKIPS a skill with no such file
    (`if (!existsSync(file)) continue`). This poller wrote state only to
    ops/runtime/, so it contributed ZERO signal and was unknown to
    dw-canary-meta-watchdog too: if it died, the 7am panel would say nothing was
    wrong — about the one app under a live Guideline 5.6 conduct citation.
    
    Worse, my previous commit's own comment claimed "alert_delivered lands in
    latest.json for fleet-health-rollup". There was no latest.json for this skill.
    I asserted an integration I never tested, in the same session spent hunting
    exactly that failure. Comment corrected; the integration now actually exists and
    is actually tested — the rollup picks the row up (verdict=ATTENTION, status=WARN).
    
    - heartbeatStatus(): approved/pending -> PASS, rejected -> WARN (matching
      ios-release-train's ATTENTION), and an outcome we could NOT measure ->
      NOT_MEASURED/WARN, never PASS (TK-11431 #1). A null state means ASC was
      unreachable or the row did not parse; that is not evidence all is well.
    - Published AFTER the durable state write and wrapped so a heartbeat failure can
      never cost the observation.
    - Never published from a --fixture run (the seam guard learned earlier today).
    - Two tests lock the mapping, incl. the negative case.
    
    TK-11155
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01BPS3gnB4H5MQKmWcHKvuf6
---
 ops/apple-review-poller.mjs      | 42 ++++++++++++++++++++++++++++++++++++----
 ops/apple-review-poller.test.mjs | 16 ++++++++++++++-
 2 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/ops/apple-review-poller.mjs b/ops/apple-review-poller.mjs
index cebb7fed..8cdd7c0a 100644
--- a/ops/apple-review-poller.mjs
+++ b/ops/apple-review-poller.mjs
@@ -68,10 +68,10 @@ function notify(state, outcome) {
   ]);
 
   // HARD RULE, mirroring _shared/alert_receipt.sh: notifying must NEVER be able to fail the
-  // caller. This runs BEFORE the log/state write (so alert_delivered lands in latest.json for
-  // fleet-health-rollup), which means a throw here would cost us the observation itself and
-  // leave a stale state file that re-fires the same "change" next run. Bookkeeping must not
-  // be able to break the measurement.
+  // caller. This runs BEFORE the state write so alert_delivered is captured in the same event
+  // that gets published as the fleet heartbeat, which means a throw here would cost us the
+  // observation itself and leave a stale state file that re-fires the same "change" next run.
+  // Bookkeeping must not be able to break the measurement.
   try {
   const helper = `${process.env.HOME}/.claude/skills/_shared/cncp_post.sh`;
   const posted = spawnSync('/bin/bash', [
@@ -104,6 +104,38 @@ export function removeScheduler(label = JOB_LABEL) {
   return spawnSync('/bin/launchctl', ['remove', label], { encoding: 'utf8' });
 }
 
+// fleet-health-rollup globs ~/.claude/skills/*/data/latest.json and SILENTLY SKIPS a skill with
+// no such file (`if (!existsSync(file)) continue`). This poller wrote its state only to
+// ops/runtime/, so it contributed ZERO signal: if it died, the 7am panel said nothing was wrong
+// — about the one app under a live Guideline 5.6 conduct citation. It was also unknown to
+// dw-canary-meta-watchdog. Caught by a red-team pass, after an earlier comment here asserted an
+// integration with the rollup that did not exist. Publishing the heartbeat IS the registration.
+//
+// verdict is the domain word; status is the PASS/WARN/FAIL the rollup reads (CLAUDE.md TK-10546).
+// An outcome we could not MEASURE is WARN, never PASS (TK-11431 #1) — a null state means ASC was
+// unreachable or the row did not parse, which is not evidence that all is well.
+export function heartbeatStatus(classification) {
+  if (!classification || classification.outcome === 'unknown') return { verdict: 'NOT_MEASURED', status: 'WARN' };
+  if (classification.outcome === 'approved') return { verdict: 'APPROVED', status: 'PASS' };
+  if (classification.outcome === 'pending') return { verdict: 'PENDING', status: 'PASS' };
+  return { verdict: 'ATTENTION', status: 'WARN' }; // rejected — real, needs a human, matches ios-release-train
+}
+
+function publishHeartbeat(event, classification) {
+  try {
+    const dir = resolve(process.env.HOME, '.claude/skills/homesonspec-apple-review/data');
+    mkdirSync(dir, { recursive: true });
+    writeFileSync(resolve(dir, 'latest.json'), `${JSON.stringify({
+      ...heartbeatStatus(classification), ts: event.checkedAt, app: APP_NAME,
+      state: event.state, previousState: event.previousState ?? null,
+      stateChanged: event.stateChanged ?? null,
+      ...(event.alert_delivered === undefined ? {} : { alert_delivered: event.alert_delivered }),
+      source: 'apple-review-poller (read-only ASC via ipa-status)',
+    }, null, 2)}\n`);
+    return true;
+  } catch { return false; } // never fail the caller over bookkeeping
+}
+
 export function run({ fixturePath = null, runtimeDir = resolve('ops/runtime') } = {}) {
   const checkedAt = new Date().toISOString();
   const logPath = resolve(runtimeDir, 'apple-review-poller.jsonl');
@@ -151,6 +183,8 @@ export function run({ fixturePath = null, runtimeDir = resolve('ops/runtime') }
   writeFileSync(statePath, `${JSON.stringify(event, null, 2)}\n`);
   process.stdout.write(`${JSON.stringify(event)}\n`);
 
+  if (!fixturePath) publishHeartbeat(event, classification);
+
   // Disarm ONLY on an approved outcome. See DISARM_OUTCOMES above: an 'attention'
   // outcome is the state we are waiting to leave, so stopping there blinds us.
   if (shouldDisarm(classification) && !fixturePath) {
diff --git a/ops/apple-review-poller.test.mjs b/ops/apple-review-poller.test.mjs
index 92e10e5c..fdae5982 100644
--- a/ops/apple-review-poller.test.mjs
+++ b/ops/apple-review-poller.test.mjs
@@ -8,7 +8,7 @@ import { existsSync } from 'node:fs';
 import { spawnSync } from 'node:child_process';
 
 import {
-  DISARM_OUTCOMES, JOB_LABEL, classifyState, parseAppState, removeScheduler, run, shouldDisarm,
+  DISARM_OUTCOMES, JOB_LABEL, classifyState, heartbeatStatus, parseAppState, removeScheduler, run, shouldDisarm,
 } from './apple-review-poller.mjs';
 
 const row = (state) => `🟡 Homes on Spec          ${state}         1.0\n`;
@@ -117,3 +117,17 @@ test('a state TRANSITION out of REJECTED is detected (this is what we are waitin
   assert.equal(moved.state, 'IN_REVIEW');
   assert.equal(moved.terminal, false, 'IN_REVIEW is Apple working again, not terminal');
 });
+
+// TK-11155 — the heartbeat must be HONEST about what it measured. Red-team found this poller
+// was invisible to fleet-health-rollup entirely; these lock the mapping so it cannot go quiet
+// or lie in the other direction (a NOT-MEASURED reading published as PASS).
+test('NEGATIVE: an unmeasurable outcome is WARN/NOT_MEASURED, never PASS', () => {
+  assert.deepEqual(heartbeatStatus(classifyState(null)), { verdict: 'NOT_MEASURED', status: 'WARN' });
+  assert.deepEqual(heartbeatStatus(undefined), { verdict: 'NOT_MEASURED', status: 'WARN' });
+});
+
+test('heartbeat maps outcomes to the fleet PASS/WARN/FAIL vocabulary', () => {
+  assert.deepEqual(heartbeatStatus(classifyState('READY_FOR_SALE')), { verdict: 'APPROVED', status: 'PASS' });
+  assert.deepEqual(heartbeatStatus(classifyState('IN_REVIEW')), { verdict: 'PENDING', status: 'PASS' });
+  assert.deepEqual(heartbeatStatus(classifyState('REJECTED')), { verdict: 'ATTENTION', status: 'WARN' });
+});

← 471d1568 Poller: notify() must never be able to fail its caller  ·  back to Homesonspec  ·  (newest)