Closes #944. _has_hard_locator accepted only repo+issue / 40-hex sha / file — the forge vocabulary. The detector emits path (+snapshot_sha when attested) and NEVER emits file/issue/sha, so predicate and sole producer shared ZERO keys and every class=actionable board_file entry dead-lettered. Latent since #920, whose harness pinned the detector's own emission shape as its malformed example — the suite certified the gap it was written to guard. Fix: `path` becomes a hard-locator arm, and ONLY path. Bare path-less snapshot_sha is a deliberate NON-arm (would widen past the board_file vocabulary); Q11(d) asserts it still quarantines at 7/40/64 chars, spanning the detector's ^[0-9a-f]{7,64}$ attestation range. VERIFICATION - Pipeline 2105 terminal-green atfa36da8. Its `test` step reaches all nine wake harnesses via turbo -> packages/mosaic `test` -> `test:framework-shell`, which names each suite explicitly. The two-levels-down indirection matters: no search of .woodpecker/* can see it, and that is exactly why this question was got wrong earlier today and then corrected. CI therefore DOES attest the quarantine suite and the detector suite at this head. - Independent review (mos-dt, consumer on the affected lane) PASS atfa36da8, from a detached worktree: predicate provably unmoved fromfb3c3c3(comment-stripped sha256 identical, _has_hard_locator body byte-identical), RED control at base reproduced exactly 8 failures all Q11 including "got 6". - Third reviewer (wake-judge) ACCEPT on both judgment calls: the Q1 assertion reversal is a legitimate correction (the flip was forced, not elective — base fixtures red 19 assertions against the head predicate) and path-alone satisfies §2.1, whose operative test is "one targeted call, never a search" — two of its four named exemplars already resolve to current state. Requiring path+snapshot_sha jointly would permanently dead-letter a declared source class and conflict with #940's advisory-fields ruling. - Judge's mutation criterion met: with the reconciled exemption disabled, the gate-level assertion ("an ORIENTATION-tier enumeration must NOT be quarantined") dies at this head and did not exist as a casualty before F1. - D4 (detector lock re-acquisition) fails intermittently at base AND head; git diff base..head over the detector files is EMPTY, so it is out of this PR's surface on structural grounds rather than on a re-roll. Known defect, fix identified (detector.sh:516, fd 9 leaked into sleep), tracked separately. KNOWN RESIDUALS - ENUM-B is now the sole address-free reconciled fixture, so the exemption's gate-level guard is a population of one. Safe by population, not by design. Author follow-up: assert ENUM-B carries no hard-locator arm so the harness guards its own premise. - The binding spec (CONVERGED-DESIGN.md §2.1, separate repo) still enumerates four forge tokens and reads narrower than the shipped gate. Tracked as #948, sequenced after the dragon-lin reseed. - Hard-locator arms are type-loose: repo/file/path accept any non-null JSON value. Pre-existing; `sha` fails closed only by accident of test(). Tracked separately. Authored by pepper (sb-it-1-dt); reviewed independently by mos-dt (sb-it-1-dt) and wake-judge. The mos-dt-0 commit/fork identity does not identify the author — attribution collapse tracked in #3092. Co-authored-by: mos-dt-0 <[email protected]>
433 lines
32 KiB
Plaintext
433 lines
32 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.
|
|
# 0.6.4 #920 digest.sh drain-quarantine + reconciler-enumeration render tier
|
|
# (live wake-pilot finding #6, BLOCKING). (a) PER-ENTRY
|
|
# QUARANTINE: a render-refused ACTIONABLE entry (no §2.1 hard
|
|
# locator) is now DEAD-LETTERED to $STATE_DIR/dead-letter.jsonl +
|
|
# a loud per-entry alarm and EXCLUDED, while the REST of the
|
|
# cumulative set still renders (exit 0). Replaces the whole-digest
|
|
# exit-4 that let ONE malformed entry wedge the entire drain (head-
|
|
# of-line blocking — 4 consecutive live timer failures, nothing
|
|
# delivered). Fail-loud is preserved, now per-entry; the bad entry
|
|
# is never silently dropped. (b) Reconciler ENUMERATIONS render
|
|
# ORIENTATION-tier: an entry whose locators carry reconciled==true
|
|
# (set only by reconcile.sh) is EXEMPT from the actionable hard-
|
|
# locator gate and renders as an orientation pointer via
|
|
# _locator_line's digest-class vocabulary — a RENDER-layer change
|
|
# only. reconcile.sh's STORE class is UNCHANGED (non-coalescing), so
|
|
# distinct enumerations never collapse (§2.3/T2/G3-R6 intact); the
|
|
# rejected class=digest alternative would have silently coalesced
|
|
# them. store.sh and reconcile.sh are UNCHANGED by 0.6.4.
|
|
# 0.6.5 #927 enqueue TOCTOU fix — move stale-tmp cleanup OFF the hot enqueue
|
|
# path (no concurrent in-flight-write clobber). cmd_enqueue called
|
|
# _wake_init_dir() (which reaped EVERY .wake.tmp.* unconditionally)
|
|
# BEFORE taking the enqueue lock, so a 2nd enqueue's PRE-LOCK cleanup
|
|
# deleted the LIVE in-flight tmp of a 1st enqueue holding the lock
|
|
# through its atomic write -> spurious "durable pending write FAILED"
|
|
# abort of a valid enqueue (reachable under live co-feed: detector +
|
|
# reconciler concurrently enqueue). FIX (_wake-common.sh): (a)
|
|
# _wake_init_dir no longer reaps tmps — it only ensures the layout,
|
|
# so nothing on the enqueue/consume/cursors/ack hot paths can clobber
|
|
# a concurrent live write; (b) _wake_clean_stale_tmp is AGE-SCOPED
|
|
# (mmin +${WAKE_TMP_STALE_MIN:-5}) so it can only remove demonstrably-
|
|
# orphaned crash-left tmps, never a live (ms-old) in-flight write.
|
|
# Reaping now runs as an explicit MAINTENANCE action at store.sh init
|
|
# (daemon-start) and the detector poll tick (detector.sh), keeping
|
|
# accumulation bounded once-per-pass instead of raced per-enqueue.
|
|
# #908 seq-integrity is UNCHANGED (single store-side allocator,
|
|
# atomic allocate+enqueue under flock, arrow-1 no-burn, anti-swallow
|
|
# fail-loud). reconcile.sh is UNCHANGED (its enumeration retry is the
|
|
# structural recovery net: an aborted enqueue advances neither the
|
|
# seen-ledger nor observed_seq, so the source is re-enumerated next
|
|
# cycle — no obligation loss). Files changed: _wake-common.sh,
|
|
# store.sh, detector.sh (+ tests).
|
|
# 0.6.6 #924 digest.sh dead-letter QUARANTINE alarm — G2a fix (wake-pilot
|
|
# cure-verification follow-up on #920/PR #922). The #920 per-entry
|
|
# quarantine alarm was stderr/journal-LOCAL only; a dead-lettered
|
|
# entry is STORE-ACCOUNTED (§2.3) so the reconciler never re-flags
|
|
# it, so journal-local-only visibility meant an unattended operator
|
|
# could PERMANENTLY MISS a real obligation (G2a silent-degradation).
|
|
# FIX: the SAME per-entry quarantine alarm now ALSO routes through
|
|
# WAKE_ALARM_SINK_CMD — REUSING beacon.sh's (W6/#910) exact
|
|
# pluggable off-host alarm-sink adapter contract (operator target
|
|
# resolved by-name inside the adapter, fail-closed) — IN ADDITION
|
|
# to (never instead of) the existing stderr diagnostic. Per-
|
|
# observed_seq DEDUP (entries carry no per-entry wake_id; the
|
|
# entry's durable identity is its store-allocated observed_seq,
|
|
# #908) via a durable alarmed-set file under STATE_DIR
|
|
# (dead-letter-alarmed.set, atomic-written) ensures a still-dead-
|
|
# lettered entry is alarmed off-host EXACTLY ONCE per drain/restart,
|
|
# never once per re-render; a NEW distinct dead-lettered entry
|
|
# still routes its own one alarm. An unconfigured/unreachable
|
|
# WAKE_ALARM_SINK_CMD is a LOUD per-entry stderr diagnostic
|
|
# (mirrors beacon.sh's fail-closed wording) but does NOT itself
|
|
# fail the whole render (per-entry fail-loud, never a whole-drain
|
|
# wedge — #920's core property is preserved). digest.sh is the
|
|
# ONLY file changed; store.sh/beacon.sh/reconcile.sh are
|
|
# UNCHANGED (beacon.sh's adapter contract is reused, not modified).
|
|
# 0.6.7 #913 wake-install.sh installer ADOPTION-GAP fixes (wake-pilot,
|
|
# non-blocking, ADDITIVE per #869). (a) DEP-CHECK: the installer
|
|
# sourced _lib/manifest.sh (the shared framework-manifest reader it
|
|
# needs for Gate A) unconditionally, so an older host seed that
|
|
# predates that helper aborted with a bare, obscure
|
|
# `source: No such file or directory`. It now checks the library
|
|
# FIRST and FAILS LOUD naming the missing file + the remedy (re-seed
|
|
# the framework, then retry --component wake) — the dependency is
|
|
# genuinely required (Gate A cannot be skipped on an enforcement
|
|
# path), so it fails loud rather than degrading. (b) SYSTEMD SEARCH
|
|
# PATH: wi_install copies mosaic-wake.service under mosaic home
|
|
# (systemd/user/, framework-owned) but `systemctl --user` searches
|
|
# ~/.config/systemd/user/, so the unit was invisible and could not be
|
|
# enabled/started. install now LINKS the unit into the user systemd
|
|
# search path (symlink -> the mosaic-home SSOT copy, so upgrades
|
|
# propagate) and VALIDATES it resolves (search-path entry exists,
|
|
# dereferences to a readable, well-formed unit; an opportunistic
|
|
# `systemctl --user cat` probe runs only behind a guard, since the
|
|
# installer may run where no user manager is live). Both steps are
|
|
# idempotent (a re-install neither duplicates nor breaks the link).
|
|
# #869 ADDITIVE: the link target lives OUTSIDE mosaic home, so it is
|
|
# not a framework-manifest path; ownership of the SSOT unit stays
|
|
# systemd/** in the single framework-manifest.txt authority — NO new
|
|
# owned path, NO second ownership authority. framework-manifest.txt,
|
|
# the install-ordering-guard, and the manifest parity contract are all
|
|
# UNCHANGED. Only wake-install.sh (+ test-wake-install.sh) changed.
|
|
# 0.6.8 #925 — framework-ship the canon-side FALLBACK WAKE (F7 replacement-
|
|
# before-retirement) so hosts get it OUT OF THE BOX rather than hand-
|
|
# wiring it per host. ADDITIVE, #869 / Gate-A/B discipline:
|
|
# (1) new framework units systemd/user/mosaic-wake-fallback.{timer,
|
|
# service}: a LOW-FREQUENCY SAFETY drain (oneshot service running the
|
|
# canon drain `digest.sh render --from-store`) fired by a per-class
|
|
# cadence timer, INDEPENDENT of the event-driven detector, so a stalled
|
|
# detector/daemon can never SILENTLY STARVE delivery. Both units are
|
|
# framework-owned via the EXISTING `systemd/**` glob in framework-
|
|
# manifest.txt (Gate A) — NO new owned path, NO second ownership
|
|
# authority. (2) an OPTIONAL, additive per-class `fallback_cadence`
|
|
# bound in wake-watch-list.schema.json (config, not code). It is
|
|
# backward-compatible within schema_version 1, so [schema_min,
|
|
# schema_max] stays [1,1] and the detector's Gate B range check is
|
|
# UNCHANGED (an out-of-range schema_version still fails loud). (3) A10
|
|
# install/wire: wi_install enumerates + LINKS + validates the two units
|
|
# into the user systemd search path (idempotent, fail-closed, same
|
|
# link-to-SSOT pattern as the detector unit); write-fallback-cadence
|
|
# writes the per-class cadence as a BLANK-RESET drop-in (exactly one
|
|
# effective OnUnitActiveUSec). (4) F7 install-validate: the §5 legacy
|
|
# reap now REFUSES unless the canon fallback wake is proven live
|
|
# (installed + schedulable floor always; enabled + proven-firing when a
|
|
# live user manager is probeable, mirroring #913's opportunistic
|
|
# WAKE_VERIFY_USE_SYSTEMCTL pattern) — F7 is encoded in the installer,
|
|
# not operator memory. framework-manifest.txt, the install-ordering-
|
|
# guard, and the manifest parity contract are all UNCHANGED.
|
|
# 0.6.9 #917 store.sh cmd_enqueue — HARDEN the final observed_seq cursor write
|
|
# (defense-in-depth, surfaced by the #915 review obs#2; non-blocking).
|
|
# The final cursor _atomic_write was the ONE durable write not wrapped
|
|
# in a failure check and was cross-file non-atomic with the observed.set
|
|
# write just before it. Now (a) the cursor write is GATED like the
|
|
# pending/observed.set writes (#908) — a cursor-write failure is
|
|
# FAIL-LOUD (non-zero + diagnostic), never silently swallowed into a
|
|
# spurious success while the allocation stayed uncommitted; and (b) on
|
|
# cursor-write failure observed.set is ROLLED BACK to its pre-write
|
|
# snapshot, so observed.set and the cursor can never be left cross-file
|
|
# inconsistent (observed.set ahead of a cursor that never committed) —
|
|
# they BOTH advance or NEITHER does. #908 is UNCHANGED: single store-side
|
|
# allocator, atomic allocate+enqueue under flock, arrow-1 no-burn
|
|
# (pending write still FIRST and its failure still aborts before any
|
|
# cursor advance), anti-swallow ≤consumed fail-loud, and the W2
|
|
# contiguous-prefix CONSUMED contract all intact. The cursor remains the
|
|
# sole COMMIT point (an uncommitted pending entry is re-derived/reconciled,
|
|
# never consumed), so a pending-ahead state is exactly the one #908 already
|
|
# tolerates on its observed.set-failure path. ON-DISK FORMAT UNCHANGED
|
|
# (read-compatible; a store written by older code reads identically). Only
|
|
# store.sh (+ test-wake-store-ack.sh T11) changed.
|
|
# 0.6.10 #932 reconciler RE-ENUMERATION of already-CONSUMED detector-observed
|
|
# state (wake-pilot finding #7 — safe-but-noisy G2a alarm-hygiene).
|
|
# After consume-truncation a consumed state matched NO accounting
|
|
# record (inbox truncated; the reconciler's seen-ledger only covers
|
|
# its OWN enumerations; the detector hash-file is correctly
|
|
# DISTRUSTED) -> the reconciler treated it as UNACCOUNTED and
|
|
# re-enumerated it: one DUPLICATE orientation wake + one SPURIOUS
|
|
# rc=1 CRITICAL per detector-active window per cycle (functionally
|
|
# safe — no lost obligation — but cry-wolf erosion of real alarms at
|
|
# fleet scale). FIX: (1) store.sh records the last-consumed
|
|
# observed_hash per (kind,id) at consume-truncation into a NEW
|
|
# store-owned durable record consumed-hashes.jsonl (atomic write;
|
|
# ADDITIVE — existing on-disk format unchanged/read-compatible; #908
|
|
# allocator untouched); (2) reconcile.sh adds a THIRD accounting
|
|
# source alongside the inbox and its seen-ledger: a detector-observed
|
|
# state whose observed_hash MATCHES the store's recorded last-consumed
|
|
# hash is ACCOUNTED (not re-enumerated — no dup wake, no spurious
|
|
# CRITICAL). TRUST BOUNDARY: the 3rd check consults ONLY the
|
|
# store-written record (its existence implies the state was durably
|
|
# enqueued+consumed, so it structurally cannot exhibit the §5
|
|
# hash-advance-without-enqueue swallow signature); trusting DETECTOR
|
|
# hash-files STAYS REJECTED. G3 is NOT weakened: only states the store
|
|
# RECORDED as consumed are suppressed — a genuinely-unaccounted state
|
|
# (enqueued-but-unconsumed, still in the inbox, OR a real gap) still
|
|
# re-enumerates + alarms. Changed: store.sh, reconcile.sh,
|
|
# _wake-common.sh (doc), test-wake-reconcile.sh (R10/R11),
|
|
# test-wake-store-ack.sh (T12).
|
|
# 0.6.11 #934 seq-integrity fault injection made MOUNT-FREE + privilege-invariant
|
|
# so the allocator's most safety-critical failure paths ACTUALLY RUN in
|
|
# the real NON-privileged CI runner (which denies mount-in-userns) instead
|
|
# of skipping. T9 (#908 arrow-1 no-burn) and T11 (#917 final-cursor gate +
|
|
# observed.set rollback) previously forced a write to fail via
|
|
# `unshare --mount --user --map-root-user` + a bind-mount EBUSY-on-mountpoint,
|
|
# which the non-priv runner DENIES -> both SKIPPED (skipped-trust-layer, the
|
|
# class #912 cured for digest). FIX: a single test-only, PROD-INERT fault
|
|
# seam in _wake-common.sh _atomic_write honored ONLY when the env var
|
|
# WAKE_TEST_FAULT explicitly names a write point (pending->pending.jsonl,
|
|
# cursor->observed_seq); it forces the ALREADY-EXISTING fail-loud/rollback
|
|
# PATH (#908/#917) to be taken for that one target and RUNS UNPRIVILEGED. No
|
|
# production input can set a process env var, so with it unset the seam is a
|
|
# no-op: on-disk format + allocator semantics are byte-for-byte unchanged in
|
|
# production. The unshare+bind-mount injection AND its skip-when-unavailable
|
|
# guard/witness-marker are REMOVED — T9/T11 now RUN and ASSERT their failure
|
|
# paths in every environment including non-priv CI. Changed: _wake-common.sh
|
|
# (seam), test-wake-store-ack.sh (T9/T11 conversion).
|
|
# 0.6.12 #940 snapshot-datable digests — the adapter-contract fd-3 snapshot-
|
|
# metadata channel (wake-pilot finding fw-wake-digest-snapshot-lag:
|
|
# a digest's locator carried observed_hash + emit_ts but nothing
|
|
# DATING the snapshot, so a consumer could not tell a fresh
|
|
# snapshot from one already superseded at delivery without a tool
|
|
# call). ADDITIVE + backward-compatible: (a) detector.sh invokes
|
|
# the W4 source adapter with fd 3 redirected to a temp file; the
|
|
# adapter MAY write one JSON object {"snapshot_sha": "<git commit
|
|
# sha>", "snapshot_ts": <epoch>} there. OUT-OF-BAND is load-
|
|
# bearing: everything on stdout is hashed by the delta gate, so an
|
|
# in-band tip-commit sha would advance observed_hash on every
|
|
# unrelated push (spurious delta wake per watched file). Metadata
|
|
# is ADVISORY and validated (sha ^[0-9a-f]{7,64}$, ts number):
|
|
# malformed metadata is dropped with a LOUD stderr diagnostic but
|
|
# NEVER fails the poll or suppresses the wake — the obligation
|
|
# never depends on optional dating. Valid fields join the enqueue
|
|
# locators; an adapter that never writes fd 3 is byte-identical
|
|
# legacy behavior. (b) digest.sh _locator_line renders
|
|
# snapshot_sha=/snapshot_ts= (scrubbed) beside observed_hash=, and
|
|
# snapshot_sha+path upgrades the one-call re-verify hint to
|
|
# `git show <snapshot_sha>:<path>` (snapshot_sha IS a commit sha,
|
|
# unlike observed_hash, so it may feed the git hint). With emit_ts
|
|
# already in the header, snapshot age becomes local arithmetic for
|
|
# the consumer — zero round trips. Watch-list schema UNTOUCHED
|
|
# ([1,1] unchanged — adapter contract + locator vocabulary, not
|
|
# watch-list config). store.sh/reconcile.sh/beacon.sh UNCHANGED.
|
|
# Review hardening (#941 §2): snapshot_ts additionally requires a
|
|
# VALID snapshot_sha (a bare number with no revision to re-verify
|
|
# against is the weakest attestation — dropped loudly), must be a
|
|
# sane positive epoch (^[0-9]{1,12}$ — validated BEFORE the shell
|
|
# integer comparison so an absurd value cannot error past it), and
|
|
# must not sit beyond a future-skew allowance
|
|
# (WAKE_SNAPSHOT_TS_FUTURE_SLACK, default 300 s): a future ts
|
|
# yields a NEGATIVE age — stale-reads-fresher-than-fresh, the
|
|
# exact failure class #940 fixes. The SLACK knob itself is
|
|
# operator input interpolated into arithmetic under set -u, so it
|
|
# gets the same discipline (#941 §2 round 2): shape-validated as
|
|
# a plain non-negative integer of at most 9 digits, else LOUD
|
|
# fallback to 300 — a malformed knob
|
|
# ('300s', '5m', 'abc') must never kill the poll, and a negative
|
|
# one must never invert the guard into deny-all. Shape validation
|
|
# is NOT radix validation (#942 review): bash reads leading zeros
|
|
# as OCTAL, so '08'/'09' pass the shape check yet are fatal in
|
|
# $((...)) and '0300' silently means 192 — the knob is therefore
|
|
# forced base-10 (10#) after validation, so it means what the
|
|
# operator wrote. The validator and the consumer must also agree
|
|
# on STRING EXTENT (#942 follow-up): grep's ^...$ anchors bind
|
|
# per LINE, so a multi-line value ($'300\n8') passed the regex
|
|
# whole yet was fatal in $((...)) — validation is a whole-string
|
|
# case pattern, not grep, so an embedded newline rejects.
|
|
# SKEW GUARANTEE
|
|
# (stated, not implied): the future-skew check runs against the
|
|
# DETECTOR's clock; consumer-side age arithmetic runs on the
|
|
# consumer's. A surviving snapshot_ts is therefore attested only
|
|
# to within SLACK seconds of the detector's clock, plus whatever
|
|
# skew the consumer's own clock adds — a small NEGATIVE age at
|
|
# render is bounded, not impossible; treat age <= 0 as
|
|
# "effectively current," never as proof of freshness.
|
|
# NOTE for consumers: these fields
|
|
# are ADVISORY and their ABSENCE IS DELIBERATELY NOT DIAGNOSTIC —
|
|
# a pre-#940 adapter and a dropped-as-malformed attestation render
|
|
# identically (no snapshot_* fields); the drop is loud only in the
|
|
# detector's own stderr. Do not build load-bearing logic on the
|
|
# absence of these fields.
|
|
# Changed: detector.sh, digest.sh (+ test-wake-detector.sh
|
|
# D10/D11/D12/D13, test-wake-digest-quarantine.sh Q10).
|
|
# 0.6.13 #942/#943 SLACK-knob validation hardening, split from 0.6.12 because
|
|
# version= is the component's SOLE self-identity claim (no per-file
|
|
# hashes here) and two detector-changing merges after the 0.6.12
|
|
# stamp had left three materially different detectors under one
|
|
# version string (#943 review §2). #942: the knob is resolved once,
|
|
# shape-validated, LOUD fallback 300, and forced base-10 (10#) so
|
|
# zero-padded values mean what the operator wrote instead of octal.
|
|
# #943: validation is a whole-string case pattern, not grep, so an
|
|
# embedded newline ($'300\n8' — accepted per-line by grep's ^...$
|
|
# anchors, fatal in $((...))) rejects. Full rationale in the knob
|
|
# paragraph of the 0.6.12 entry above.
|
|
# Changed: detector.sh (+ test-wake-detector.sh D13).
|
|
# 0.6.14 #944 the §2.1 hard-locator gate was UNSATISFIABLE for detector-built
|
|
# actionable board_file entries: _has_hard_locator tested only
|
|
# repo+issue / 40-hex sha / file, while detector.sh (A1) builds
|
|
# kind/id/observed_hash + path (+ snapshot_sha/_ts when attested,
|
|
# #940) — no key in common, so every class=actionable board_file
|
|
# delta was structurally guaranteed to dead-letter (live: mos-dt
|
|
# seqs 63/68, 2026-07-30; the only tier with a 30m SLO delivered
|
|
# nothing on its only actionable source). Fix: `path` becomes a
|
|
# hard-locator arm — and ONLY path: it mirrors `file`'s one-call
|
|
# "re-read X" precision, upgrading to one-call
|
|
# `git show <snapshot_sha>:<path>` when a snapshot is attested.
|
|
# observed_hash (content hash, not an address) and bare path-less
|
|
# snapshot_sha (would widen the gate past the board_file
|
|
# vocabulary — review-adopted criterion) remain NON-arms.
|
|
# Quarantine is a RENDER-TIME filter (entries never leave
|
|
# pending), so existing UNCONSUMED dead-letters re-deliver
|
|
# automatically on the first post-upgrade drain; consumed-past
|
|
# dead-letters are not requeued.
|
|
# Changed: digest.sh (+ test-wake-digest-quarantine.sh: Q11
|
|
# positive control — the live seq-68 entry verbatim must RENDER
|
|
# as CLAIM@seq — and Q1/Q6-Q9 fixtures moved off the now-valid
|
|
# path-bearing shape onto genuinely address-free shapes,
|
|
# amending the #920-era ruling that had pinned the live pilot's
|
|
# own locator shape as the malformed example). Doc follow-up:
|
|
# #948 amends CONVERGED-DESIGN.md §2.1 to add `path` to the
|
|
# hard-locator enumeration and to state the operative test as
|
|
# "one targeted call, never a search" (NOT "pins the observed
|
|
# state") — sequenced AFTER the reseed so the edit itself is a
|
|
# live delivery test of the fixed gate.
|
|
component=wake
|
|
version=0.6.14
|
|
|
|
# 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.
|
|
#
|
|
# #925: the OPTIONAL per-class `fallback_cadence` bound is ADDITIVE and backward-
|
|
# compatible — an existing schema_version-1 watch-list stays valid (the field is
|
|
# omittable), so the supported range is UNCHANGED at [1, 1] and Gate B is intact.
|
|
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. Stale-tmp reaping is
|
|
# OFF the hot enqueue path; `init` performs the age-scoped
|
|
# maintenance reap (#927). enqueue's final observed_seq cursor
|
|
# write is GATED fail-loud + rolls observed.set back on failure so
|
|
# the two never diverge (#917). (W2, #927, #917)
|
|
# 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). PER-ENTRY
|
|
# quarantine: a render-refused entry is dead-lettered +
|
|
# alarmed (stderr AND off-host via WAKE_ALARM_SINK_CMD,
|
|
# deduped by observed_seq, #924) + excluded, the rest still
|
|
# renders (no head-of-line block); reconciler enumerations
|
|
# (reconciled==true) render ORIENTATION-tier, gate-exempt.
|
|
# (W3, #920, #924)
|
|
# 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). Its poll tick also
|
|
# runs the age-scoped maintenance stale-tmp reap (#927). (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). Fails loud
|
|
# if the required _lib/manifest.sh helper is absent (older host
|
|
# seed) instead of a bare source error, and LINKS the unit into
|
|
# the user systemd search path + post-install-validates it
|
|
# resolves (#913). (W7, #913)
|
|
# 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)
|
|
# Companion (framework subtree, not under tools/wake/):
|
|
# systemd/user/mosaic-wake-fallback.timer + mosaic-wake-fallback.service
|
|
# — the canon FALLBACK WAKE (F7): a per-class cadence timer firing
|
|
# a oneshot SAFETY drain (digest.sh render --from-store),
|
|
# INDEPENDENT of the detector, so a stalled detector cannot starve
|
|
# delivery. Owned via the existing systemd/** glob; the per-class
|
|
# cadence is a blank-reset drop-in; the §5 reap is F7-gated on this
|
|
# being proven live. (W7, #925)
|