[object Object]

← back to Tk 10965 Zero Price Analysis

make zero-price canary scheduler-visible

2a6ecb4e03e1e56a86d3622658366c23a64a8225 · 2026-08-30 14:31:26 -0700 · Steve Abrams

Files touched

Diff

commit 2a6ecb4e03e1e56a86d3622658366c23a64a8225
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 30 14:31:26 2026 -0700

    make zero-price canary scheduler-visible
---
 tk10964-canary-guard/README.md                     |  4 ++++
 tk10964-canary-guard/check.mjs                     | 10 +++++++-
 .../com.steve.zero-price-orderable-canary.plist    | 28 ++++++++++++++++++++++
 tk10964-canary-guard/test.mjs                      |  5 +++-
 4 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/tk10964-canary-guard/README.md b/tk10964-canary-guard/README.md
index 3bb528f..d17c6cf 100644
--- a/tk10964-canary-guard/README.md
+++ b/tk10964-canary-guard/README.md
@@ -8,3 +8,7 @@ the cause.
 
 Use `--fixture fixtures/mixed.json` for a network-free end-to-end output test.
 Live mode performs GraphQL queries only and writes one local JSON status file.
+
+Exit codes are scheduler-visible: `0` PASS, `2` WARN, `3` FAIL, and `1` for
+transport/runtime failure. The installed LaunchAgent runs daily at 06:35 local
+time under `com.steve.zero-price-orderable-canary`.
diff --git a/tk10964-canary-guard/check.mjs b/tk10964-canary-guard/check.mjs
index a1b9e39..8a93b81 100644
--- a/tk10964-canary-guard/check.mjs
+++ b/tk10964-canary-guard/check.mjs
@@ -56,6 +56,12 @@ function writeResult(result) {
   console.log(`${result.verdict}: ${result.zero_price_orderable} zero-price-orderable of ${result.searched_active_unique} uniquely searched active`);
 }
 
+export function exitCodeFor(result) {
+  if (result.verdict === 'PASS') return 0;
+  if (result.verdict === 'WARN') return 2;
+  return 3;
+}
+
 async function liveProducts() {
   const env = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
   const val = key => (env.match(new RegExp(`^${key}=(.*)$`, 'm')) || [])[1]?.trim();
@@ -86,7 +92,9 @@ async function liveProducts() {
 async function main() {
   const fixtureArg = process.argv.indexOf('--fixture');
   const products = fixtureArg >= 0 ? JSON.parse(fs.readFileSync(process.argv[fixtureArg + 1], 'utf8')) : await liveProducts();
-  writeResult(summarize(products));
+  const result = summarize(products);
+  writeResult(result);
+  process.exitCode = exitCodeFor(result);
 }
 
 if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) {
diff --git a/tk10964-canary-guard/com.steve.zero-price-orderable-canary.plist b/tk10964-canary-guard/com.steve.zero-price-orderable-canary.plist
new file mode 100644
index 0000000..99f275e
--- /dev/null
+++ b/tk10964-canary-guard/com.steve.zero-price-orderable-canary.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>Label</key>
+  <string>com.steve.zero-price-orderable-canary</string>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/opt/homebrew/bin/node</string>
+    <string>/Users/macstudio3/.claude/skills/zero-price-orderable-canary/check.mjs</string>
+  </array>
+  <key>RunAtLoad</key>
+  <false/>
+  <key>StartCalendarInterval</key>
+  <dict>
+    <key>Hour</key>
+    <integer>6</integer>
+    <key>Minute</key>
+    <integer>35</integer>
+  </dict>
+  <key>ProcessType</key>
+  <string>Background</string>
+  <key>StandardOutPath</key>
+  <string>/tmp/zero-price-orderable-canary.log</string>
+  <key>StandardErrorPath</key>
+  <string>/tmp/zero-price-orderable-canary.err</string>
+</dict>
+</plist>
diff --git a/tk10964-canary-guard/test.mjs b/tk10964-canary-guard/test.mjs
index 0b6b353..6d316ae 100644
--- a/tk10964-canary-guard/test.mjs
+++ b/tk10964-canary-guard/test.mjs
@@ -1,7 +1,7 @@
 #!/usr/bin/env node
 import assert from 'node:assert/strict';
 import fs from 'node:fs';
-import { SEARCHES, inScope, summarize } from './check.mjs';
+import { SEARCHES, exitCodeFor, inScope, summarize } from './check.mjs';
 
 const products = JSON.parse(fs.readFileSync(new URL('./fixtures/mixed.json', import.meta.url)));
 const result = summarize(products, '2026-08-30T00:00:00.000Z');
@@ -14,4 +14,7 @@ assert.equal(result.zero_price_orderable, 2);
 assert.deepEqual(result.by_vendor, { 'Fentucci Naturals': 1, 'Phillipe Romano': 1 });
 assert.deepEqual(result.mechanism, { qty_2026: 2, deny: 2, tracked: 2 });
 assert.equal(result.verdict, 'WARN');
+assert.equal(exitCodeFor({ verdict: 'PASS' }), 0);
+assert.equal(exitCodeFor({ verdict: 'WARN' }), 2);
+assert.equal(exitCodeFor({ verdict: 'FAIL' }), 3);
 console.log('PASS: broad searches, Fentucci blind spot, dedupe, defect predicate, and controls');

← db7f993 snapshot live zero-price evidence before canary install  ·  back to Tk 10965 Zero Price Analysis  ·  auto-data-snapshot: 2026-08-30T14:42:48 (4 data files) — tk1 7e1dd31 →