← back to Dw Yolo Loop

scripts/canary-install/INSTALL-RUNBOOK.md

117 lines

# Catalog-Integrity Guard — INSTALL RUNBOOK (paste-and-go, Steve-gated) — cycle 79

**DTD verdict C (unanimous 3/3).** READ-ONLY authoring (files written in the worktree; **NOTHING installed** — every install step is a command YOU run). Converts the two built-but-dark guards into a turn-on-in-minutes package. Both guards currently protect NOTHING because nothing schedules them — this closes that gap.

## The guard set (built this run, hardened, officer-signed)
| Guard | Watches | Built/hardened | State now |
|---|---|---|---|
| **c77 sitemap-integrity** | population-collapse (the April-2026 mass-archive failure mode) | `sitemap-integrity-canary.mjs` (baseline 72,462) | HEALTHY — **ready to install now** |
| **c78 zero-dollar-orderable** | free-checkout (orderable $0 variants, P1) | `zero-dollar-orderable-canary.mjs` + `ack-baseline.json` (153 known-open tuples, c80) | **INSTALLABLE NOW** — set-difference ack-baseline alerts only on a NEW regression (see §3) |
| c75 dw-map-auditor extension | price/mislabel/disco (spec) | spec-only | not built yet (future cycle) |

All three use the existing `scripts/promote-canary.sh` (writes the plist, PRINTS the bootstrap — never bootstraps; install is Steve-gated by design) + the new `scripts/canary-install/run-canary-with-cncp.sh` wrapper (adds the CNCP-alert-on-exit-3 that the bare canary lacks).

## §1 — One-time: move the canaries to a durable main-repo location
The canaries live on the loop's worktree branch (`dw-yolo-loop`); install should point at a stable path, not a dev worktree. Copy them into the main repo:
```sh
mkdir -p ~/Projects/designerwallcoverings/scripts/sitemap-integrity-canary \
         ~/Projects/designerwallcoverings/scripts/zero-dollar-orderable-canary \
         ~/Projects/designerwallcoverings/scripts/canary-install
cp ~/Projects/dw-yolo-loop/scripts/sitemap-integrity-canary/{sitemap-integrity-canary.mjs,baseline.json} \
   ~/Projects/designerwallcoverings/scripts/sitemap-integrity-canary/
cp ~/Projects/dw-yolo-loop/scripts/zero-dollar-orderable-canary/{zero-dollar-orderable-canary.mjs,ack-baseline.json} \
   ~/Projects/designerwallcoverings/scripts/zero-dollar-orderable-canary/   # ack-baseline.json = the 153 known-open tuples (c80)
cp ~/Projects/dw-yolo-loop/scripts/canary-install/run-canary-with-cncp.sh \
   ~/Projects/dw-yolo-loop/scripts/canary-install/run-sitemap-integrity.sh \
   ~/Projects/dw-yolo-loop/scripts/canary-install/run-zero-dollar.sh \
   ~/Projects/designerwallcoverings/scripts/canary-install/
chmod +x ~/Projects/designerwallcoverings/scripts/canary-install/*.sh
```
> NOTE: `promote-canary.sh` runs its `$SCRIPT` arg **with no args** (and `[ -f ]`-checks it), so we schedule the **thin no-arg wrappers** (`run-sitemap-integrity.sh` / `run-zero-dollar.sh`) — each `exec`s the generic `run-canary-with-cncp.sh` with its fixed canary+json+label+urgency. Do NOT pass the generic wrapper + args as one string to promote-canary.sh (it would fail the file check).

## §2 — Install c77 sitemap-integrity (READY NOW — it's HEALTHY)
```sh
# (a) sanity-run it once by hand (read-only; should print VERDICT: HEALTHY, exit 0)
node ~/Projects/designerwallcoverings/scripts/sitemap-integrity-canary/sitemap-integrity-canary.mjs; echo "rc=$?"

# (b) schedule the THIN no-arg wrapper daily at 06:10 via the existing harness
bash ~/Projects/dw-yolo-loop/scripts/promote-canary.sh dw-sitemap-integrity \
  "$HOME/Projects/designerwallcoverings/scripts/canary-install/run-sitemap-integrity.sh" daily 6 10
#   ^ promote-canary.sh writes ~/Library/LaunchAgents/com.steve.dw-sitemap-integrity.plist + PRINTS the bootstrap cmd
#     (the thin wrapper exec's run-canary-with-cncp.sh with the fixed canary+json+label+urgency)

# (c) VALIDATE the plist with PlistBuddy (c56 lesson — NOT plutil-lint alone):
P=~/Library/LaunchAgents/com.steve.dw-sitemap-integrity.plist
/usr/libexec/PlistBuddy -c "Print :Label" "$P"            # expect com.steve.dw-sitemap-integrity
/usr/libexec/PlistBuddy -c "Print :ProgramArguments" "$P" # expect the wrapper+canary invocation
/usr/libexec/PlistBuddy -c "Print :StartCalendarInterval" "$P"  # expect Hour 6 Minute 10

# (d) ENABLE (Steve runs):
launchctl bootstrap gui/$(id -u) "$P"
launchctl list | grep dw-sitemap-integrity      # expect a line
launchctl kickstart -k gui/$(id -u)/com.steve.dw-sitemap-integrity   # run-now to confirm
```
- Alert wiring: on a crater/non-200/stale (exit 3) the wrapper POSTs a CNCP card; HEALTHY = silent.
- **Re-baseline only after an APPROVED large catalog change** (e.g. the Thibaut 122 unpublish): `rm baseline.json` then run once to re-seed, so a sanctioned change isn't a standing near-threshold alarm. (The canary already upward-ratchets on growth.)

## §3 — c78 zero-dollar-orderable: INSTALLABLE NOW (the ack-baseline is coded + seeded — c80)
- **The acknowledged-baseline is DONE (c80).** `ack-baseline.json` is seeded with the 153 known-open tuples (145 active + 8 draft), keyed `handle#position#status`. A normal run classifies hits by **set-difference**: acknowledged tuples are tracked quietly; it ALERTs (exit 3) ONLY on a **NEW** tuple (a regression / bad import / a draft→active escalation, since the status is in the key). So it no longer daily-alerts on the known-open set → **install now**, it guards against the 146th.
- **Install (same mechanics as §2):**
  ```sh
  bash ~/Projects/dw-yolo-loop/scripts/promote-canary.sh dw-zero-dollar \
    "$HOME/Projects/designerwallcoverings/scripts/canary-install/run-zero-dollar.sh" daily 6 20
  # PlistBuddy-validate + launchctl bootstrap as in §2(c)(d); add the §5 hbAbs watchdog entry.
  ```
  Make sure `ack-baseline.json` is copied alongside the canary in §1 (it lives in the same dir).
- **Re-baseline to 0 after the gated reprice:** once Steve clears the 145+8, `rm ack-baseline.json` (or re-run `ACK_SEED=1` → it'll seed 0) so the guard's "acknowledged" set is empty and ANY future $0-orderable fires. Verify the reprice via a manual run first.
- **Modes:** `ACK_SEED=1 node …` (re-acknowledge the current set); `SELFTEST=1 node …` (instant logic check, no scan — proves NEW fires + acknowledged quiet + draft→active escalation fires).
- **Prior fallback (if you'd rather not run with an ack-baseline):** reprice first, then install with `ack-baseline.json` deleted (absolute-gate mode: alerts on any hit). The ack-baseline path above is preferred — it protects during the gated-reprice window.
- **⚠️ The ack-baseline is a TEMPORARY reprice-shim, NOT a permanent fixture (c80 officer):** the `handle#position#status` grain cannot distinguish "still-open" from "fixed-then-identically-re-broke" — if a repriced handle is later re-imported at the SAME position+status with $0, it reuses an acknowledged tuple and is silently acknowledged. Bounded + narrow, but the rule is: **`rm ack-baseline.json` right after the gated reprice** (→ absolute-gate, every $0 fires) so this window can't hide a re-break.
- **Expect occasional benign NEW fires (c80 officer):** because position is in the key, a benign variant-reorder of an acknowledged $0 (`#3#ACTIVE`→`#4#ACTIVE`) or an active→draft re-draft fires as NEW. This fails SAFE (alerts, never silently passes); just re-baseline (`ACK_SEED=1`) after a known benign reorder.
- Either way the install mechanics are identical to §2 (promote-canary.sh + wrapper + PlistBuddy + bootstrap).
- **Nuance (officer):** c78's coverage floor is `MIN_ACTIVE_SCAN=50000`. If the active catalog ever dips near that (e.g. *during* an archive event), c78 flips to INCONCLUSIVE (exit 2, posts a card) — that's the gate refusing to trust a shrunken scan, NOT the free-checkout failure it watches. Read an INCONCLUSIVE card as "scan was incomplete/small," and cross-check the c77 sitemap-integrity crater alarm (the actual population-collapse detector).

## §4 — Disable / uninstall (any guard)
```sh
launchctl bootout gui/$(id -u)/com.steve.<label>
rm ~/Library/LaunchAgents/com.steve.<label>.plist
```

## §5 — Heartbeat / meta-watchdog (REQUIRED — else liveness-blind, officer)
Both canaries write `data/latest.json` under their script dir. But `dw-canary-meta-watchdog`'s `REGISTRY` (`~/.claude/skills/dw-canary-meta-watchdog/watchdog.mjs`) resolves heartbeats only via `skill:<name>` (→ `~/.claude/skills/<name>/data/latest.json`) or `hbAbs:<abspath>`. These canaries are NEITHER a skill dir NOR registered → **without an explicit `hbAbs:` entry the watchdog ignores them (or, with a naive `skill:` entry, reports "never ran" forever).** Add these two REGISTRY entries (absolute heartbeat paths) when you install:
```js
{ label:'com.steve.dw-sitemap-integrity', hbAbs:`${process.env.HOME}/Projects/designerwallcoverings/scripts/sitemap-integrity-canary/data/latest.json`, every:'daily', maxAgeMs: 30*60*60*1000 },
{ label:'com.steve.dw-zero-dollar',       hbAbs:`${process.env.HOME}/Projects/designerwallcoverings/scripts/zero-dollar-orderable-canary/data/latest.json`, every:'daily', maxAgeMs: 30*60*60*1000 },
```
(30h max-age = a daily job + slack. Add the zero-dollar entry only when you install c78 per §3.) Without this, the new guards have NO liveness coverage — the exact silent-death blind spot they were built to close.

## Notes / guardrails carried from the build cycles
- **Read-only / no prod write:** both canaries only GET the public sitemap (c77) or run read-only Admin GraphQL queries (c78). The wrapper only POSTs to local CNCP. No mutation anywhere. Install (launchctl) is the only privileged step and it's yours.
- **PlistBuddy not plutil-lint** (c56): plutil only checks XML well-formedness; PlistBuddy asserts the keys actually resolve.
- **promote-canary.sh never bootstraps** (by design + classifier-blocked) — it writes the plist + prints the command; you run the bootstrap.
- These two guards cover the two highest-severity classes this run found (mass-archive + free-checkout) that the existing fleet (c63/64/65 SEO, uptime, scraper, leak, map-auditor) did NOT watch.

_Cycle 79. DTD-picked C (unanimous 3/3). Wrote the paste-and-go install runbook + a CNCP-alert wrapper (`run-canary-with-cncp.sh`) so the c77 + c78 guards become installable by Steve in minutes via the existing `promote-canary.sh` harness. c77 = install now (HEALTHY); c78 = install after the gated reprice (or after the set-difference ack-baseline is added) to avoid daily-alert-on-the-known-open-P1. All steps are commands Steve runs (install Steve-gated); authoring was read-only in the worktree, $0. PlistBuddy-validate per c56; wrapper adds the CNCP-on-exit-3 the bare canaries lack; heartbeat wired for the meta-watchdog._

---
## 🏛️ OFFICER SIGN-OFF — vp-engineering: REVISE (chain correct; 2 install-correctness gaps) → fixed + re-verified
The officer traced the full chain + read the watchdog registry. Chain + exit-propagation CONFIRMED correct; two gaps fixed in-cycle:
- **Chain + exit propagation CONFIRMED:** promote-canary.sh `*.sh→/bin/bash <wrapper>` + `[ -f ]` passes (bare thin-wrapper path) → thin wrapper `exec`s the generic wrapper → `node "$CANARY"; rc=$?` (left of `;`, so `set -uo pipefail` doesn't abort) → `exit "$rc"` propagates 0/2/3 to launchd. CNCP-down can't kill the wrapper (`curl -s …` no `-e`) and the exit code still reaches launchd/watchdog. The `node -e require(json)` summary read handles missing/garbage json (catch → fallback).
- **REVISE #1 — CNCP payload (FIXED + re-verified):** the heredoc-interpolated body could be malformed if the summary held a `"`/newline. → rebuilt the body via `node JSON.stringify` (safe escaping) + `[^\x20-\x7E]→space` (plain-ASCII) + 400-cap + truncate-on-start for the wrapper log. **Smoke-tested live:** a summary containing `\"orderable\"` + `& <special>` → valid JSON → live CNCP POST **http=200**. The {url,note} shape is the same one this run POSTed ~15× successfully. Wrapper now also logs the http code so a future 400/000 is visible.
- **REVISE #2 — meta-watchdog liveness (FIXED in runbook §5):** the canaries' `data/latest.json` is neither a skill dir nor in the watchdog REGISTRY → as written the watchdog would report them "never ran" forever. §5 now gives the exact `hbAbs:` REGISTRY entries (abs heartbeat path, 30h max-age) to add at install — closing the silent-death blind spot the guards exist to close.
- **Sequencing CONFIRMED:** c77-now (confirm-before-page + abort-on-partial + crater-floor + ratchet = low FP on a fresh schedule); c78-after-reprice (else daily-alert-on-the-open-145+8). Added the §3 nuance: c78's MIN_ACTIVE_SCAN floor → INCONCLUSIVE during a shrink event (refusing a small scan, not the failure it watches).
- **Minor (addressed):** log-growth → truncate-on-start added to the wrapper. No relative-cwd bug (both canaries use `fileURLToPath`-derived absolute paths under launchd's `/` cwd).

REVISE → chain merge-ready; the two gaps (live-verified CNCP payload + explicit watchdog hbAbs entries) fixed so a clean install is also alert-wired AND liveness-covered. Install remains Steve-gated (every launchctl/registry-edit step is his to run).

---
## 🏛️ OFFICER SIGN-OFF (c80 — ack-baseline) — vp-engineering: CONFIRMED (1 verification gap closed in-cycle + 2 notes)
The c80 enhancement makes c78 installable-now via a set-difference acknowledged-baseline. Officer review:
- **Set-difference logic CORRECT, `handle#position#status` grain right for its core jobs:** verified against the real seeded ack-baseline (153 unique tuples, 145 ACTIVE + 8 DRAFT, no cross-status collisions). Stays quiet on the known 145+8; a new $0 import = new handle = fires; draft→active escalation fires (status in key); SELFTEST proves all three (NEW=1, ack=2, escalation fires → PASS). Faithfully implements the c78/c79 set-difference (NOT count-delta) prescription.
- **VERIFICATION GAP (the officer's one ask) — CLOSED in-cycle:** the "post-seed run = HEALTHY" claim was asserted from logic+SELFTEST, not executed. → **ran a real post-seed normal run (full 71,733 active + 6,784 draft scan, complete, coverage gate PASS): `acknowledged-pending-fix: 153 | NEW (beyond acknowledged): 0` → VERDICT: HEALTHY (exit 0).** Asserted → VERIFIED. The canary now sits HEALTHY with the 153 known-open quietly tracked, and would exit-3 only on a NEW tuple.
- **Acknowledged-tuple-reuse false-clean (bounded) → documented as a TEMPORARY shim:** the grain can't distinguish still-open from fixed-then-identically-re-broke; mitigated by the standing `rm ack-baseline.json` after the reprice. Added the "temporary reprice-shim, not a permanent fixture" note to §3.
- **Status-in-key false-POSITIVE noise (fails SAFE):** a benign variant-reorder/re-draft of an acknowledged $0 fires as NEW (alerts, never silently passes). Added the "expect occasional benign NEW fires, re-baseline after a known reorder" note to §3.
- **ACK_SEED-on-incomplete correctly refused** (exits 2, never enshrines a partial crawl); re-baseline-to-0 coherent (rm → absolute-gate); token-missing → INCONCLUSIVE not false-HEALTHY; coverage floor decoupled from RL (survives the reprice).

CONFIRMED — set-difference faithful, the only gap (asserted-vs-verified HEALTHY) closed by a real post-seed run, two grain-limitation notes added to §3. c78 is installable-now (Steve-gated) and protects against a NEW free-checkout regression during the gated-reprice window.