All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Make the wake digest/HMAC suite RUN and PASS in the real Woodpecker CI
runner (Alpine/musl, root), then hard-require the HMAC legs in CI.
Root cause of the runner-only D1/D4/D5/D6 failures (a masked-local run
passed, so it was runner-specific): a TOOLCHAIN divergence, not locale or
root. digest.sh's _scrub_ctrl used GNU-sed `\xNN` hex-escape byte matching.
The CI runner is node:24-alpine, whose sed is BusyBox — BusyBox sed REJECTS
a `\xNN` character range ("bad regex ... Invalid character range"), aborting
the whole scrub sed and silently VOIDING the scrub. Every scrubbed value
collapsed to empty, cascading into D1 (blank locators), D4 (no scrub/redact,
SHA blanked), D5 (blank agent prefix), D6 (blank [digest] class). Confirmed
by reproducing the exact 9-assertion failure in the ci-base image as root.
Fix (at the correct layer — a wake digest must render identically on any
runner):
- digest.sh _scrub_ctrl: patterns are now LITERAL bytes (printf %b), matching
byte-identically under GNU sed (glibc dev) and BusyBox sed (Alpine CI).
Verified identical output on both. Contract preserved: two-tier trust,
exit-4 hard-locator FAIL-LOUD, secret-scrub, and 40-hex SHA preservation
all unchanged — deterministic, not weakened.
- test-wake-digest-hmac.sh D4: replaced PCRE `grep -qP` (BusyBox grep has no
-P; the `&&` silently skipped the check in CI) with portable literal-byte
`grep -E` ranges (two disjoint bidi/zero-width ranges, excluding legit
U+2014 em-dash).
CI enablement:
- Dockerfile.ci + .woodpecker/ci.yml test step: add openssl (the non-circular
HMAC signer) so H1/H2, beacon B12, install I8 can run. The apk add in the
test step covers PR pipelines before ci-base rebuilds.
- Flip the 3 openssl skip-guards (digest whole-file, beacon B12, install I8)
to HARD-REQUIRE openssl when CI is set (Woodpecker CI=woodpecker) and FAIL
loud if absent; KEEP the skip for openssl-less local dev.
- manifest.txt: wake 0.6.2 -> 0.6.3 (digest.sh scrub portability; precedent).
Red-first verified in the ci-base container (root): D4 catches a broken
redaction, H1 catches a tamper that doesn't break the MAC, B12/I8 catch a
corrupted signer.
Closes #912
Part of #892
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
116 lines
7.9 KiB
Plaintext
116 lines
7.9 KiB
Plaintext
# Mosaic wake component — VERSION metadata manifest (Gate B).
|
|
#
|
|
# EPIC #892, W2 + W3 of the wake/heartbeat canon.
|
|
#
|
|
# SCOPE — THIS FILE IS VERSION METADATA ONLY. It declares the wake component's
|
|
# semantic version and the RANGE of watch-list schema versions it supports. It
|
|
# does NOT authorize file/path ownership: path-ownership remains the sole domain
|
|
# of packages/mosaic/framework/framework-manifest.txt (Gate A). Do not read any
|
|
# ownership meaning into this file.
|
|
#
|
|
# Format: KEY=VALUE, one per line. '#' and blank lines ignored.
|
|
|
|
# Component identity + semantic version.
|
|
# 0.1.0 W2 — store+drain lib + ack-wrapper.
|
|
# 0.2.0 W3 — cumulative-state digest renderer + non-circular HMAC signer.
|
|
# 0.3.0 W4 — per-host single-instance delta-gated detector daemon.
|
|
# 0.4.0 W5 — synthetic-canary FN-oracle + source-parity reconciler.
|
|
# 0.5.0 W6 — off-host dead-man beacon emitter + pluggable alarm-sink adapter
|
|
# + beacon-absence alarm (fail-loud on unconfigured/unreachable).
|
|
# 0.6.0 W7 — A10 idempotent, fail-closed component installer (Gate-A
|
|
# intersect+validate against the framework-manifest SSOT), the
|
|
# mosaic-wake.service detector daemon, the blank-reset retire idiom
|
|
# for the legacy heartbeat timer + snapshot-guard, and fail-closed
|
|
# alarm-target/HMAC-key install-validation. Also folds in the two W6
|
|
# monitor-integration observations (monitor-side ingested_ts
|
|
# staleness + beacon HMAC-verify at record).
|
|
# 0.6.1 #908 — UNIFY observed_seq on a SINGLE store-side allocator. store.sh
|
|
# enqueue is now the sole allocator (reads its own cursor, next=+1
|
|
# under an exclusive lock, prints the seq; commits IFF the durable
|
|
# write succeeds). The detector-private observed_seq_counter and its
|
|
# --seq hand-off are DELETED; the reconciler enumerates via the same
|
|
# store allocator (its dual-allocator fail-closed guard retired). This
|
|
# dissolves the three defects rooted in the private-counter seam:
|
|
# burn-before-enqueue (arrow 1), W5 co-feed aliasing (arrow 2), and
|
|
# the migration-restart silent-swallow (arrow 3, now structurally
|
|
# impossible — allocation is always > consumed or fails loud).
|
|
# 0.6.2 #914 digest.sh renderer fixes (live wake-pilot findings): (a) the
|
|
# embedded ack copy-run line now bakes an explicit
|
|
# WAKE_AGENT=<render-time-agent> prefix (shell-quoted) so an
|
|
# env-less copy-run resolves to the correct per-agent namespace
|
|
# instead of silently falling back to `default`; (b) the
|
|
# ORIENTATION locator renderer now also recognizes the locator
|
|
# vocabulary detector.sh (A1) actually emits for a digest-class
|
|
# entry (kind/id/observed_hash/remote/path), so a digest-class
|
|
# pointer carries a usable (soft) locator instead of rendering
|
|
# empty. Display-only: the ACTIONABLE-tier hard-locator FAIL-LOUD
|
|
# gate (_has_hard_locator, exit 4) is unchanged.
|
|
# 0.6.3 #912 digest.sh scrub PORTABILITY (no contract change): _scrub_ctrl's
|
|
# control/bidi/zero-width byte patterns are now LITERAL bytes (via
|
|
# printf %b) instead of GNU-sed `\xNN` hex escapes. BusyBox sed (the
|
|
# Alpine/musl CI runner, running as root) rejects a `\xNN` character
|
|
# range, which aborted the whole scrub sed and silently VOIDED the
|
|
# scrub in CI — collapsing every scrubbed value to empty and failing
|
|
# the digest suite's D1/D4/D5/D6 only in the Woodpecker runner. The
|
|
# scrub now renders byte-identically under GNU sed (glibc dev) and
|
|
# BusyBox sed (Alpine CI). The two-tier trust, exit-4 hard-locator
|
|
# FAIL-LOUD, and the secret-scrub/SHA-preservation contract are all
|
|
# unchanged — this makes the existing scrub deterministic across
|
|
# runners, it does not weaken it.
|
|
component=wake
|
|
version=0.6.3
|
|
|
|
# Watch-list schema this component consumes, and the INCLUSIVE range of
|
|
# schema_version values it supports. A wake-watch-list.json whose schema_version
|
|
# falls outside [schema_min, schema_max] is rejected by the component (fail-loud),
|
|
# never silently coerced.
|
|
schema=wake-watch-list
|
|
schema_min=1
|
|
schema_max=1
|
|
|
|
# Pieces shipped by this component version (informational):
|
|
# store.sh A2 — three-cursor durable store + drain lib. (W2)
|
|
# ack.sh A4 — RECEIVED/CONSUMED ack-wrapper (local-write + ship). (W2)
|
|
# digest.sh A3 — cumulative-state digest renderer (hard locators,
|
|
# two-tier trust, injection/secret scrub). (W3)
|
|
# sign.sh A5 — non-circular HMAC signer (independent wake_id,
|
|
# load_credentials by-name; fills the hmac placeholder). (W3)
|
|
# detector.sh A1 — per-host single-instance delta-gated detector daemon
|
|
# (flock, anchor-scoped hashing, fail-loud source semantics;
|
|
# enqueues deltas to store.sh and captures the store-allocated
|
|
# observed_seq — no private counter, #908). (W4)
|
|
# fn-oracle.sh A6 — synthetic-canary FN-oracle: injects a KNOWN delta at the
|
|
# source boundary, drives the pipeline through the detector's
|
|
# public poll-once, asserts CONSUMED within the per-class SLO
|
|
# (off-domain verdict from the terminal store cursor). §4
|
|
# requires FN-rate=0; a dropping/disabled detector FAILS. (W5)
|
|
# reconcile.sh A7 — source-parity reconciler: (i) source-coverage parity
|
|
# inventory (an omitted source cannot pass the vector
|
|
# vacuously) + (ii) periodic full reconcile to 0-unaccounted,
|
|
# enumerating pre-existing/startup state into the store via the
|
|
# SINGLE store-side allocator (co-feed is safe; the former
|
|
# dual-allocator fail-closed guard retired, #908). (W5)
|
|
# beacon.sh A8 — off-host DEAD-MAN liveness beacon: a monotonic beacon
|
|
# EMITTER (emit — the primitive the detector run-loop calls
|
|
# each cycle), the off-host monitor's RECEIVER + beacon-ABSENCE
|
|
# alarm (record, check), and a pluggable alarm-sink/beacon-sink
|
|
# ADAPTER INTERFACE. Liveness is SPLIT from work-triggering;
|
|
# the alarm fires on ABSENCE, routing to a human/other-host
|
|
# within its SLO (§4/G1). FAIL-CLOSED: an unconfigured OR
|
|
# unreachable target FAILS LOUD (no silent no-alarm host).
|
|
# A same-host sibling is REJECTED as non-independent; an
|
|
# isolated host degrades to a FLAGGED different-supervision-root
|
|
# beacon; capture-pane is a liveness HINT only. (W6)
|
|
# wake-install.sh A10 — idempotent, fail-closed COMPONENT installer. Selects the
|
|
# component file set and INTERSECTS-AND-VALIDATES it against the
|
|
# single SSOT framework-manifest.txt (Gate A) — this VERSION
|
|
# manifest authorizes no path. Ships the blank-reset retire
|
|
# idiom (exactly-one OnUnitActiveUSec) for the legacy heartbeat
|
|
# timer, the snapshot-guard (no reap without a snapshot), and
|
|
# fail-closed alarm-target + HMAC-key install-validation (the
|
|
# installer wires + install-validates the beacon target that
|
|
# beacon.sh's fail-loud primitive is designed for). (W7)
|
|
# Companion (framework subtree, not under tools/wake/): systemd/user/mosaic-wake.service
|
|
# — the long-lived detector daemon unit (per-class SLO lives in
|
|
# the daemon, NOT a systemd interval). (W7)
|