feat(wake): W4 — per-host delta-gated detector daemon (fail-loud source semantics, enqueues to W2 store) #907

Merged
Mos merged 1 commits from feat/wake-detector into main 2026-07-26 01:04:56 +00:00
Owner

W4 — per-host delta-gated detector daemon (A1)

Builds on merged W2 (store/ack/schema) + W3 (digest/sign) on main. The detector OBSERVES sources and ENQUEUES deltas to the W2 store via its public API — it does not reimplement store/ack/digest/sign.

Design map

  • §1.1 placementdetector.sh run is a per-host single-instance (non-blocking flock; a 2nd instance refuses loudly), long-lived service (not a timer). Serves local panes only; no cross-host waking. Generalizes the proven poll → hash → deliver-only-on-delta poller and adds (a) repo-section/anchor-scoped hashing (_scope_anchor) so human-decision FILE edits are caught, not just API-visible state.
  • §1.2 observe→store — on an observed delta, assign the next observed_seq and store.sh enqueue --seq --class --locators --emit-ts (W2 store, not reimplemented). Class passes through from the watch-list source; absent ⇒ store defaults to actionable (fail-safe).
  • §2.4 cursor semantics — detector-local observed_seq is a monotonic int assigned at observation (single global counter, authoritative); source SHAs are descriptors only. A per-watch last-observed hash is compared each poll, so a revert A→B→A across polls is caught as a delta.
  • §4 / G2a fail-loud source semantics — a source error, 401/403/privacy-404/partial (adapter non-zero exit), or ambiguous-empty (exit 0 + empty output that might mean "hidden") fails loud, does NOT advance observed_seq, does NOT touch the last-observed hash, and is never treated as "no change." A missing anchor is treated the same way.
  • Gate B (#869 discipline) — the watch-list schema_version is validated against the component manifest's [schema_min, schema_max]; out of range ⇒ rejected (fail loud), never coerced.
  • G3 parity — only sources declared under watches[].sources[] are polled; a referenced-but-undefined source id fails loud.

The source adapter (WAKE_DETECTOR_SOURCE_CMD) is pluggable so the framework bakes in no git/gitea/HTTP/file specifics (operator-agnostic firewall). All state is XDG/env; no operator paths/names/secrets/hosts.

RED-first evidence

test-wake-detector.sh (wired into test:framework-shell), fake/stub sources only, shellcheck clean:

Invariant Test
no-change poll → NO enqueue (delta-gated) D1
a change → exactly ONE enqueue with a fresh observed_seq D2
revert A→B→A across polls → caught D3
single-instance flock (2nd refuses) D4
schema_version out of range → FAIL LOUD D5
source error / ambiguous-empty → FAIL LOUD, observed_seq NOT advanced (G2a) D6
anchor-scoped hashing (outside=no-op, inside=delta) D7

Teeth verified by mutation: breaking the delta-gate, silencing the G2a fail-loud, and removing the flock guard each turn the harness RED; restored = GREEN. The harness also caught two real bugs during development (a jq reserved-word def collision and a pipefail SIGPIPE on the adapter stdin).

Gates / firewall

  • shellcheck clean (detector.sh + harness); format:check (prettier) passes.
  • Firewall grep -niE 'jason|woltje|jarvis' = 0; verify-sanitized.sh gate passed.
  • All three wake harnesses green (store-ack, digest-hmac, detector).

Out of scope (untouched)

FN-oracle/reconciler (W5), off-host beacon (W6), installer (W7). store.sh/ack.sh/digest.sh/sign.sh called only via public APIs.

Contract note (flagged, not guessed)

First-seen baseline: on the first observation of a source (no prior hash) the detector establishes the baseline without a wake, matching the deliver-on-NEW exemplar §1.1 generalizes. Startup obligations that predate the detector are the reconciler's job (W5, out of scope). Flagged in case the canon intends first-seen to wake.

Part of #892

## W4 — per-host delta-gated detector daemon (A1) Builds on merged W2 (store/ack/schema) + W3 (digest/sign) on main. The detector OBSERVES sources and ENQUEUES deltas to the W2 store via its public API — it does not reimplement store/ack/digest/sign. ### Design map - **§1.1 placement** — `detector.sh run` is a per-host **single-instance** (non-blocking `flock`; a 2nd instance refuses loudly), long-lived **service** (not a timer). Serves local panes only; no cross-host waking. Generalizes the proven `poll → hash → deliver-only-on-delta` poller and adds **(a) repo-section/anchor-scoped hashing** (`_scope_anchor`) so human-decision FILE edits are caught, not just API-visible state. - **§1.2 observe→store** — on an observed delta, assign the next `observed_seq` and `store.sh enqueue --seq --class --locators --emit-ts` (W2 store, not reimplemented). Class passes through from the watch-list source; absent ⇒ store defaults to `actionable` (fail-safe). - **§2.4 cursor semantics** — detector-local `observed_seq` is a monotonic int assigned **at observation** (single global counter, authoritative); source SHAs are descriptors only. A per-watch **last-observed hash** is compared each poll, so a revert **A→B→A across polls** is caught as a delta. - **§4 / G2a fail-loud source semantics** — a source error, `401`/`403`/privacy-`404`/partial (adapter non-zero exit), or **ambiguous-empty** (exit 0 + empty output that might mean "hidden") **fails loud, does NOT advance `observed_seq`, does NOT touch the last-observed hash, and is never treated as "no change."** A missing anchor is treated the same way. - **Gate B (#869 discipline)** — the watch-list `schema_version` is validated against the component manifest's `[schema_min, schema_max]`; out of range ⇒ **rejected (fail loud)**, never coerced. - **G3 parity** — only sources declared under `watches[].sources[]` are polled; a referenced-but-undefined source id fails loud. The source adapter (`WAKE_DETECTOR_SOURCE_CMD`) is pluggable so the framework bakes in **no** git/gitea/HTTP/file specifics (operator-agnostic firewall). All state is XDG/env; no operator paths/names/secrets/hosts. ### RED-first evidence `test-wake-detector.sh` (wired into `test:framework-shell`), fake/stub sources only, shellcheck clean: | Invariant | Test | |---|---| | no-change poll → NO enqueue (delta-gated) | D1 | | a change → exactly ONE enqueue with a fresh `observed_seq` | D2 | | revert A→B→A across polls → caught | D3 | | single-instance flock (2nd refuses) | D4 | | schema_version out of range → FAIL LOUD | D5 | | source error / ambiguous-empty → FAIL LOUD, `observed_seq` NOT advanced (G2a) | D6 | | anchor-scoped hashing (outside=no-op, inside=delta) | D7 | Teeth verified by mutation: breaking the delta-gate, silencing the G2a fail-loud, and removing the flock guard each turn the harness RED; restored = GREEN. The harness also caught two real bugs during development (a jq reserved-word `def` collision and a `pipefail` SIGPIPE on the adapter stdin). ### Gates / firewall - shellcheck clean (`detector.sh` + harness); `format:check` (prettier) passes. - Firewall `grep -niE 'jason|woltje|jarvis'` = 0; `verify-sanitized.sh` gate passed. - All three wake harnesses green (store-ack, digest-hmac, detector). ### Out of scope (untouched) FN-oracle/reconciler (W5), off-host beacon (W6), installer (W7). store.sh/ack.sh/digest.sh/sign.sh called only via public APIs. ### Contract note (flagged, not guessed) **First-seen baseline:** on the first observation of a source (no prior hash) the detector establishes the baseline **without** a wake, matching the deliver-on-NEW exemplar §1.1 generalizes. Startup obligations that predate the detector are the reconciler's job (W5, out of scope). Flagged in case the canon intends first-seen to wake. Part of #892
jason.woltje added 1 commit 2026-07-26 00:53:05 +00:00
A1 of the wake canon (EPIC #892). A per-host, single-instance, long-lived
detector SERVICE (not a timer) that generalizes the proven delta-gated poller
and enqueues observed deltas into the merged W2 durable store.

- §1.1 placement: per-host single-instance (non-blocking flock; a 2nd instance
  refuses), serves local panes only, no cross-host waking. A long-lived service,
  not a timer. Generalizes the poll->hash->deliver-only-on-delta exemplar and
  adds (a) repo-section/anchor-scoped hashing so human-decision FILE edits are
  caught, not just API-visible state.
- §1.2 observe->store: on a delta, assign the next observed_seq and enqueue to
  store.sh with {class, locators, emit_ts} via its public API (store not
  reimplemented).
- §2.4 cursor semantics: detector-local observed_seq is a monotonic int assigned
  AT OBSERVATION and authoritative; source SHAs are descriptors. A per-watch
  last-observed hash is compared each poll so a revert (A->B->A) is caught.
- §4/G2a fail-loud source semantics: a source error / 401 / 403 / privacy-404 /
  partial / ambiguous-empty response fails loud, does NOT advance observed_seq,
  and is NEVER treated as "no change".
- Gate B (#869): the watch-list schema_version is validated against the manifest
  [schema_min, schema_max]; out of range => rejected (fail loud), never coerced.

RED-first harness (test-wake-detector.sh), wired into test:framework-shell:
no-change->no enqueue; change->exactly one enqueue with a fresh observed_seq;
revert A->B->A caught; single-instance flock; schema out-of-range fail-loud;
source error/ambiguous-empty fail-loud with observed_seq not advanced;
anchor-scoped hashing (outside-anchor edit = no-op, inside-anchor edit = delta).
Fake/stub sources only (no live network); shellcheck clean; operator-agnostic.

Part of #892

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
Author
Owner

Record of Review — PR #907 W4 (per-host delta-gated detector daemon; Part of #892)

VERDICT: APPROVE (GO on CI terminal-green). Reviewer: independent subagent abd9a153 (opus, per CONVERGED-DESIGN §1.1/§1.2/§2.4/§4-G2a/Gate-B) — ≠ builder (mosaic-coder). REVIEWED-HEAD (full-40): 2ea938efdd4222b71f0a4532f1b2bb628762700d.

Per-criterion (all 10 PASS on merits; red-first teeth proven by 3 independent mutations)

  1. Delta-gate — no-change→no-enqueue; change→exactly-one (D1/D2; mutation red).
  2. observed_seq — detector-local monotonic, assigned AFTER the delta gate; source SHA rides as descriptor only.
  3. Revert A→B→A — per-watch last-observed hash catches it (D3).
  4. Flock single-instance — 2nd instance refuses (D4; mutation red).
  5. Schema fail-loud (Gate B) — schema_version outside manifest [1,1] → non-zero, no enqueue/cursor-advance (D5).
  6. ★ G2a source fail-loud — adapter non-zero / 401/403/privacy-404/partial / ambiguous-empty / missing-anchor → FAIL LOUD, observed_seq + last-hash untouched, never "no change" + recovery-proof (D6; silence-branch mutation red).
  7. Anchor-scoped hashing — hashes the scoped anchor region (marker→next heading), not raw state; outside=no-op, inside=delta (D7).
  8. ★ FIRST-SEEN BASELINE — ADJUDICATED CORRECT: do NOT wake on baseline. First observation writes a DURABLE baseline hash + returns 0 without enqueue — matches §1.1 deliver-on-NEW + §4-G3/§7 (pre-existing/startup = W5 reconciler). Baseline durable ⇒ once-per-source not per-restart; waking would be a wake-storm (the exact false-wake the design eliminates).
  9. Store-API discipline — only public store.sh enqueue/cursors; store/ack/digest/sign/_wake-common byte-UNCHANGED vs main; scope = detector.sh + test + manifest bump + package.json.
  10. Gates — shellcheck clean; firewall 0 + no operator hosts/paths; verify-sanitized pass; prettier clean; test wired into test:framework-shell (all 3 wake harnesses green); mergeable.

Noted non-blocker (reviewer: NOT a change request)

_next_observed_seq increments before store enqueue; on an (effectively-unreachable) enqueue failure a burned seq never reaches observed.set → a theoretical contiguous-prefix gap. Unreachable on the validated call path; monotonic-not-gapless arguably spec-compliant for observed_seq. Awareness only.
GO for id-11 at FULL-40 2ea938efdd4222b71f0a4532f1b2bb628762700d on CI terminal-green. Non-executor.

# Record of Review — PR #907 W4 (per-host delta-gated detector daemon; Part of #892) **VERDICT: APPROVE** (GO on CI terminal-green). **Reviewer:** independent subagent `abd9a153` (opus, per CONVERGED-DESIGN §1.1/§1.2/§2.4/§4-G2a/Gate-B) — ≠ builder (`mosaic-coder`). **REVIEWED-HEAD (full-40):** `2ea938efdd4222b71f0a4532f1b2bb628762700d`. ## Per-criterion (all 10 PASS on merits; red-first teeth proven by 3 independent mutations) 1. **Delta-gate** — no-change→no-enqueue; change→exactly-one (D1/D2; mutation red). 2. **observed_seq** — detector-local monotonic, assigned AFTER the delta gate; source SHA rides as descriptor only. 3. **Revert A→B→A** — per-watch last-observed hash catches it (D3). 4. **Flock single-instance** — 2nd instance refuses (D4; mutation red). 5. **Schema fail-loud (Gate B)** — schema_version outside manifest [1,1] → non-zero, no enqueue/cursor-advance (D5). 6. **★ G2a source fail-loud** — adapter non-zero / 401/403/privacy-404/partial / ambiguous-empty / missing-anchor → FAIL LOUD, observed_seq + last-hash untouched, never "no change" + recovery-proof (D6; silence-branch mutation red). 7. **Anchor-scoped hashing** — hashes the scoped anchor region (marker→next heading), not raw state; outside=no-op, inside=delta (D7). 8. **★ FIRST-SEEN BASELINE — ADJUDICATED CORRECT: do NOT wake on baseline.** First observation writes a DURABLE baseline hash + returns 0 without enqueue — matches §1.1 deliver-on-NEW + §4-G3/§7 (pre-existing/startup = W5 reconciler). Baseline durable ⇒ once-per-source not per-restart; waking would be a wake-storm (the exact false-wake the design eliminates). 9. **Store-API discipline** — only public store.sh enqueue/cursors; store/ack/digest/sign/_wake-common byte-UNCHANGED vs main; scope = detector.sh + test + manifest bump + package.json. 10. **Gates** — shellcheck clean; firewall 0 + no operator hosts/paths; verify-sanitized pass; prettier clean; test wired into test:framework-shell (all 3 wake harnesses green); mergeable. ## Noted non-blocker (reviewer: NOT a change request) `_next_observed_seq` increments before store enqueue; on an (effectively-unreachable) enqueue failure a burned seq never reaches observed.set → a theoretical contiguous-prefix gap. Unreachable on the validated call path; monotonic-not-gapless arguably spec-compliant for observed_seq. Awareness only. **GO for id-11 at FULL-40 `2ea938efdd4222b71f0a4532f1b2bb628762700d` on CI terminal-green. Non-executor.**
Mos approved these changes 2026-07-26 01:04:54 +00:00
Mos left a comment
First-time contributor

GO — Gate-16 id-11 stamp, pinned to exact FULL head 2ea938efdd.
Basis: verbatim RoR (comment 18933) — independent review APPROVE, all 10 criteria, red-first teeth proven via 3 mutations; FIRST-SEEN baseline adjudicated correct (do-not-wake, durable once-per-source baseline — pre-existing state is the W5 reconciler's job; waking would be the wake-storm class the design kills); G2a source fail-loud verified (401/403/ambiguous-empty never silently no-change, observed_seq not advanced); store-API discipline confirmed (prior W-layers byte-unchanged). Seq-burn wedge class tracked as #908 per Mos ruling. CI terminal-green. W4 of EPIC #892. Named executor: Mos (id-11), squash pinned.

GO — Gate-16 id-11 stamp, pinned to exact FULL head 2ea938efdd4222b71f0a4532f1b2bb628762700d. Basis: verbatim RoR (comment 18933) — independent review APPROVE, all 10 criteria, red-first teeth proven via 3 mutations; FIRST-SEEN baseline adjudicated correct (do-not-wake, durable once-per-source baseline — pre-existing state is the W5 reconciler's job; waking would be the wake-storm class the design kills); G2a source fail-loud verified (401/403/ambiguous-empty never silently no-change, observed_seq not advanced); store-API discipline confirmed (prior W-layers byte-unchanged). Seq-burn wedge class tracked as #908 per Mos ruling. CI terminal-green. W4 of EPIC #892. Named executor: Mos (id-11), squash pinned.
Mos merged commit 5df47e735e into main 2026-07-26 01:04:56 +00:00
Mos deleted branch feat/wake-detector 2026-07-26 01:04:57 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#907