Audience: creative-ops and Android UI engineers who receive .9.png drops from Figma or texture pipelines and need the same binary acceptance every night on a remote Mac. Goal: treat exports like a tiny CI service—folder watch (debounced), single-flight jobs, skirt pixel checks for true black markers, naming templates per density, byte and disk ceilings, classified retries, and JSONL logs you can gzip into cold storage. Pair the geometry rules with the 2026 nine-patch delivery checklist; pair the orchestration patterns with PNG watch, retry & log archive and OpenClaw PNG QA batch checks.
Table of Contents
Watchdog mental model & OpenClaw role
A watchdog is not “AI that draws nine-patches.” It is a state machine glued to the filesystem: events arrive noisy (partial saves, duplicate writes, sync tools), you collapse them into one job per batch, run deterministic validators, then promote or quarantine artifacts. OpenClaw fits as the orchestration surface—calling only allowlisted tools with frozen arguments—while macOS gives you stable paths for launchd, SSH, and Apple Silicon raster behavior. Cron alone misses sub-minute bursts; a debounced watch plus queue matches how designers actually export.
Directory layout & debounce rules
Create a job root on NVMe, for example ~/nine_jobs/<campaign_id>/, with inbox, work, out, failed, quarantine, logs, archive. Never point watchers at iCloud-resolved placeholders. Operational defaults that survive handoffs:
- Quiet window: after the last qualifying write, wait roughly 30–45 seconds before moving a batch from
inboxtowork(tune per exporter). - Stable size probe: two identical
statresults ≥400 ms apart before parsing pixels. - Single-flight lock: one mutex per
campaign_id; logcoalesced_eventswhen rapid saves collapse. - Ignore list: skip
.DS_Store,*.tmp,*~, and zero-byte stubs.
Install and health-check the host using the OpenClaw install guide (all platforms) before you depend on Gateway calls from non-interactive shells.
Reproducible steps (copy-paste skeleton)
Replace bracketed placeholders with your campaign values; keep scripts under git next to the OpenClaw skill manifest so diffs tell you what ran in production.
- Initialize tree
export NINE_ROOT="${HOME}/nine_jobs/<CAMPAIGN_ID>" mkdir -p "${NINE_ROOT}"/{inbox,work,out,failed,quarantine,logs,archive} - Start a debounced watcher (pattern only—pin your implementation in repo):
fswatch -l 2.0 -o "${NINE_ROOT}/inbox" | while read -r _; do "${NINE_ROOT}/bin/debounced_enqueue.sh" done - Drain the queue worker (separate process so Gateway/tool limits stay bounded):
"${NINE_ROOT}/bin/worker_once.sh" --max-files 32 --trace-id "$(uuidgen)" - Validate skirt pixels for each candidate (your script should decode RGBA and scan the outer ring):
python3 "${NINE_ROOT}/bin/validate_patch_skirt.py" \ --path "${NINE_ROOT}/work/<ASSET>.9.png" \ --require-srgb-icc true \ --jsonl-out "${NINE_ROOT}/logs/validate.jsonl" - Rename on success using the studio template (see naming table):
mv "${NINE_ROOT}/work/<ASSET>.9.png" \ "${NINE_ROOT}/out/ui__<SLUG>__<DENSITY>.9.png" - Append audit line (one JSON object per line; rotate daily):
printf '%s\n' "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"event\":\"promote\",\"asset\":\"<SLUG>\",\"bytes\":<N>,\"trace_id\":\"<UUID>\"}" \ >> "${NINE_ROOT}/logs/audit-$(date -u +%Y-%m-%d).jsonl" - Archive promoted batches:
tar -czf "${NINE_ROOT}/archive/$(date -u +%Y-%m-%d)_<BATCH_ID>.tar.gz" -C "${NINE_ROOT}/out" .
For naming discipline across other PNG classes, reuse field rules from PNG auto-naming & batch validation.
Acceptance threshold table
Use this as the pass/fail contract between design and build; tune numbers per game or app, but keep the columns so CI can diff policy YAML over time.
| Gate | Measurement | Example pass threshold | On fail |
|---|---|---|---|
| Skirt purity (control ring) | Outer 1 px rows/cols: RGBA samples | Markers exactly #000000 opaque; other ring cells alpha=0 |
quarantine/ + data class (no blind retry) |
| Anti-alias dust | Any gray 0<alpha<255 on ring | 0 offending pixels | Data class; attach PNG path in JSONL |
| Corner transparency | Four corners of ring | Fully transparent | Data class |
| Per-file byte ceiling | Filesystem size | mdpi ≤256 KiB; xxhdpi ≤1.5 MiB (example) | Quarantine + notify |
| Batch volume | Sum of bytes in current dequeue | ≤120 MiB per batch (example) | Pause queue; operator resume |
| Free disk watermark | df on job volume |
≥15% free and ≥25 GiB absolute floor | Pause global dequeue |
| ICC / color policy | Embedded profile id | sRGB IEC61966-2.1 unless signed P3 exception | Route to ICC fix playbook or quarantine |
Naming template contract
Predictable names make Gradle merges and CDN keys boring—in a good way. Standardize on machine-parseable tokens and reject human drift at promotion time.
| Token | Meaning | Example |
|---|---|---|
ui__<slug>__<density>.9.png |
Module + asset slug + mdpi/hdpi/xhdpi/xxhdpi | ui__hud_frame__xxhdpi.9.png |
<slug>_@<scale>x.9.png |
Alternate studio style | [email protected] |
sha256 (sidecar) |
Manifest line per promote | manifest.jsonl alongside out/ |
Failure classes, retries & log archive
Mirror API-style reliability inside the worker:
- Transient: file busy, short read, flaky SMB volume → exponential backoff with jitter, max attempts (for example five), each attempt appends JSONL with
next_eligible_at. - Data: skirt purity, ICC breach, template mismatch → no automatic retry; move to
quarantinewithreason_codeand require a human-edited manifest flag to requeue. - Operational: disk watermark, missing Python env, Gateway 401 → pause dequeue globally until
resumeafter fix; logpause_reason.
Log shape (one JSON line per attempt): trace_id, batch_id, asset, class, exit_code, duration_ms, stderr_tail, bytes_in, bytes_out. Rotate logs/*.jsonl daily; gzip files older than seven days into archive/logs/ so SSD pressure stays flat. The same structure appears in the Lottie export watchdog article—keep one ops vocabulary across motion and static PNG pipelines.
Gateway scopes (least privilege)
OpenClaw should orchestrate, not become a root shell. Bind the Gateway to 127.0.0.1, load tokens from a file readable only by the worker user, and allowlist ~/nine_jobs/<campaign_id>/** plus the pinned validator scripts from git. Prefer explicit tool entries such as “run validate_patch_skirt.py with argv from job YAML” over ad-hoc shell strings. Log every tool invocation with the same trace_id as the queue for security review.
Operator checklist
- Job root lives on fast local disk; no Desktop/Documents sync on the watch path.
- Single watcher PID recorded at boot; no duplicate
fswatch+ GUI sync on the same tree. - Quiet window + stable size probe enabled;
.doneor manifest rule documented in README. - Skirt validator pinned in
requirements.txtor lockfile; same version in CI and remote Mac. - Threshold YAML committed; Grafana or mailhook wired to
pause_reasonevents. - JSONL rotation + gzip cron or launchd job installed; restore drill tested quarterly.
- Gateway allowlists reviewed when campaign paths change.
FAQ
We already eyeball nine-patches in Figma—why automate the ring?
Human review misses single-pixel gray dust that shifts padding by one dp on certain panels. A decoder-aligned scan is repeatable at 02:00 and produces receipts in JSONL.
OpenClaw logs 401 from launchd but not from an interactive shell—why?
Non-interactive sessions often lack the same env files or keychain items. Reproduce with the exact same ssh command Jenkins uses, confirm token path permissions, and align HOME with the worker user.
Should the watcher call ImageMagick for every pixel test?
Use whatever decoder your Android build trusts (often Pillow or pngdecode via a pinned Python). ImageMagick is fine if versions match CI; the important part is decoding RGBA the same way validators and devices expect, not whichever CLI is shortest to type.
Can we merge this queue with HEIC or Lottie pipelines?
Share the logging and archive patterns, but keep separate inbox roots so nine-patch geometry rules do not inherit unrelated retries or byte budgets.
Summary: document the tree, debounce noisy exports into single-flight jobs, validate the black-line skirt with machine thresholds, enforce naming templates and byte/disk gates, classify failures for sane retries, and treat JSONL + gzip as your flight recorder. When you want an always-on Apple Silicon worker instead of leaving laptops un-sleepable, browse rental and purchase options and nodes & pricing on MacPng—no login is required to compare plans—and use the SSH / VNC setup guide to attach a host. Continue from Tech Insights for more OpenClaw PNG automation.
Run nine-patch QA watchers on a dedicated remote Mac
Keep .9.png acceptance off designer laptops, pin validator versions on Apple Silicon, and share queue + JSONL runbooks across time zones.