Compare commits

..
Author SHA1 Message Date
Jason WoltjeandClaude Fable 5 aef5304379 fix(wake): #941 review §2 round 2 — validate WAKE_SNAPSHOT_TS_FUTURE_SLACK before arithmetic
The operator's slack knob was interpolated raw into $((...)) under set -u:
'300s'/'5m' -> 'value too great', 'abc' -> 'unbound variable' — all FATAL to
the poll, falsifying the PR's own 'poll never fails' invariant, and a negative
value silently inverted the guard into deny-all. Now resolved once into a
local, validated ^[0-9]{1,9}$, loud fallback to 300 on mismatch. New D13
group: '300s'/'abc'/negative all fall back and KEEP valid metadata; a valid
tightened knob (0) still rejects a future ts. Manifest states the skew
guarantee explicitly: a surviving snapshot_ts is attested only to within SLACK
seconds of the DETECTOR's clock — bounded negative age at render is possible;
treat age<=0 as effectively-current, never proof of freshness.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
2026-07-30 05:55:10 -05:00
Jason WoltjeandClaude Fable 5 a54d539eb3 fix(wake): #941 review §2 — snapshot_ts hardening: sha precondition, epoch sanity, future-skew rejection
ci/woodpecker/pr/ci Pipeline was successful
A ts without a valid sha is unverifiable dating — dropped loudly. A future
ts yields a negative age (stale reads fresher-than-fresh, the failure class
this PR exists to fix) — rejected beyond WAKE_SNAPSHOT_TS_FUTURE_SLACK
(default 300s). Epoch sanity (^[0-9]{1,12}$) is validated BEFORE the shell
integer comparison so an absurd value cannot error past the guard. All three
paths covered by new D12. Manifest now records that snapshot_* absence is
deliberately not diagnostic (reviewer observation, PR #941 comment 19475).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
2026-07-30 05:42:01 -05:00
Jason WoltjeandClaude Fable 5 bb54e09aec feat(wake): #940 snapshot-datable digests — adapter fd-3 snapshot-metadata channel
ci/woodpecker/pr/ci Pipeline was successful
A digest 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 (wake-pilot finding
fw-wake-digest-snapshot-lag: seq 56 rendered "(none)" from a board revision
superseded ~5s before delivery by one adding a FLEET STOP block).

Additive + backward-compatible:
- detector.sh: invoke the W4 source adapter with fd 3 -> temp file; the
  adapter MAY write {"snapshot_sha": "<git commit sha>", "snapshot_ts":
  <epoch>}. Out-of-band is load-bearing: stdout is hashed by the delta
  gate, so an in-band tip-commit sha would advance observed_hash on every
  unrelated push. Metadata is ADVISORY and validated (sha ^[0-9a-f]{7,64}$,
  ts number); malformed metadata drops LOUDLY but never fails the poll or
  suppresses the wake. Valid fields join the enqueue locators. An adapter
  that never writes fd 3 is byte-identical legacy behavior.
- digest.sh: _locator_line renders snapshot_sha=/snapshot_ts= (scrubbed)
  beside observed_hash=; snapshot_sha+path upgrades the actionable-tier
  re-verify hint to one-call `git show <snapshot_sha>:<path>`. With
  emit_ts already in the header, snapshot age is local arithmetic.
- tests: detector D10 (fields land in locators; metadata-only change is
  NOT a delta) + D11 (malformed metadata: loud drop, wake still fires, no
  fields); digest Q10 (orientation dating render, no-vestige sibling,
  actionable re-verify upgrade). manifest 0.6.12; watch-list schema
  untouched ([1,1]); store/reconcile/beacon unchanged.

Closes #940

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
2026-07-30 05:13:47 -05:00
11 changed files with 42 additions and 1023 deletions
@@ -39,7 +39,6 @@ overwritten on upgrade. (Layer model: `constitution/LAYER-MODEL.md`.)
| TypeScript strict typing | `guides/TYPESCRIPT.md` |
| QA / test strategy | `guides/QA-TESTING.md` |
| Documentation (any code/API/auth/infra change) | `guides/DOCUMENTATION.md` |
| Writing style (docs, comms, any prose) | `guides/WRITING-STYLE.md` |
| Secrets / vault usage | `guides/VAULT-SECRETS.md` |
| Tool/credential reference (service CLIs, wrappers) | `guides/TOOLS-REFERENCE.md` |
| Memory protocol (OpenBrain capture/recall) | `guides/MEMORY.md` |
@@ -27,14 +27,6 @@ Master/slave model:
- Do not perform destructive git/file actions without explicit instruction.
- Browser automation (Playwright, Cypress, Puppeteer) MUST run in headless mode. Never launch a visible browser — it collides with the user's display and active session.
### Output standards (writing + code)
- Technical documentation follows **MOS-STE** (Mosaic Simplified Technical English — an adapted ASD-STE100 profile): short sentences, one instruction per sentence, active voice, one word per meaning, one term per concept. Full rules: `~/.config/mosaic/guides/WRITING-STYLE.md`.
- Apply MOS-STE **hardest to verification artifacts** (acceptance criteria, witness predicates, gate/alarm conditions). There an ambiguous term produces a false green, not just a confused reader.
- Source code follows the **Google Style Guide** for the language.
- User-facing comms follow the user's declared `communicationStyle` in `USER.md` "Communication Preferences" (`direct` | `friendly` | `formal`, default `direct`); `guides/WRITING-STYLE.md` §5 maps each value to output. The documentation standard does not change with user preference.
- **Carve-out:** MOS-STE does NOT apply to content that must carry a specific human voice (letters, personal or marketing prose, voice-matched output). A declared voice profile wins.
### Secrets handling (HARD RULE)
- Vault is the canonical source-of-truth for every secret in every environment. No exceptions.
@@ -1,134 +0,0 @@
# Writing Style Standard — MOS-STE (MANDATORY)
This guide defines how agents write. It sets one style standard per output type.
It is written in the standard it defines, as a worked example.
**Adapted, not compliant.** MOS-STE (Mosaic Simplified Technical English) is an
adapted profile of ASD-STE100. Mosaic does not license or certify against
ASD-STE100. Mosaic uses the load-bearing rules and fits them to agent work. This
is the same stance Mosaic takes toward DO-178B/C: use the rigor, do not claim the
certification.
## Scope — which standard governs which output
| Output type | Standard |
| ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Technical documentation (READMEs, runbooks, PRDs, procedures, ADRs, guides, acceptance criteria, design docs) | **MOS-STE** (this guide) |
| Source code and code comments | **Google Style Guide** for the language (§4) |
| Inter-agent comms | MOS-STE by default (concise, structured) |
| User-facing comms | **Per-user style choice** — read `USER.md` "Communication Preferences" (§5) |
| End-user prose the user owns (marketing, letters, personal writing, voice-matched content) | The user's declared voice. MOS-STE does NOT apply. |
**The user-voice carve-out is absolute.** Do not apply MOS-STE to content that
must carry a specific human voice (for example a cover letter, a personal
message, or marketing copy). That content needs the user's voice. MOS-STE would
damage it. When a project declares a voice profile, that profile wins.
## 1. Why one standard
Agent documentation drifts across projects. Different agents use different terms,
sentence styles, and structures for the same concept. Readers lose time.
Assumptions hide in ambiguous prose. One standard gives agents a clear target. It
gives reviewers a clear test.
## 2. Where MOS-STE matters most — verification artifacts
Apply MOS-STE hardest to acceptance criteria, witness predicates, gate
definitions, and alarm conditions. In prose, an ambiguous term produces a
confused reader. In a verification artifact, an ambiguous term produces a false
green — a check that passes without testing the claim.
The one-term-one-concept rule (rule 9) is the guard. When one word names two
concepts in one predicate, the check can test the wrong concept and still pass.
**Worked failure.** A rename used a witness predicate with three clauses: ref A
present, ref B absent, tip committed from this host. Every clause tested the git
_ref_ (the channel). The claim under test was about a _field inside the payload_.
The word "beacon" named two concepts in one sentence. Deleting ref B was the next
scheduled step. That step flips the last clause green and certifies a state in
which the payload still names the wrong host. The predicate was one planned action
away from a false green on its normal path. The payload field was never tested.
Rule: when N failure modes share one observable, the observable is not a
diagnostic. In a verification artifact, that ambiguity does not confuse a reader —
it certifies the defect.
## 3. MOS-STE rules
### 3.1 Sentence rules
1. Keep sentences short. Use 20 words or fewer for a procedure. Use 25 words or
fewer for a description. (Reasoning and doctrine prose relaxes this limit —
see §3.4. A future lint enforces §3.1, not §3.4.)
2. Write one instruction per sentence. In a procedure, give one command per step.
3. Use the active voice. Write "Run the script." Do not write "The script should
be run."
4. Use the imperative for instructions. Start the sentence with the verb.
5. Use simple verb tenses. Prefer the present tense. Avoid the perfect and
progressive tenses when a simple tense works.
6. Do not use an `-ing` form when it makes the meaning unclear.
7. Write positive statements. State what to do, not only what to avoid.
### 3.2 Word rules
8. Use one word for one meaning. Do not use the same word in two senses.
9. Use one term for one concept. Do not use synonyms for variety. Example: choose
`secret`, `credential`, or `key` for each concept, and keep it.
10. Use articles (`a`, `the`). Do not drop words to save space.
11. Keep an approved-terms glossary per project. Add each domain noun and each
chosen verb. Technical names (for example `Vault`, `cgroup`, `systemd`) are
always allowed.
12. Define an abbreviation at its first use. Then use it consistently.
### 3.3 Structure rules
13. Use a list for parallel items or sequential steps. Do not put them in one long
sentence.
14. Use a table for data with more than two dimensions.
15. Use parallel structure in headings and steps.
16. Repeat the noun. Do not use a pronoun when the reference is unclear.
### 3.4 Adaptation notes (where MOS-STE deviates from ASD-STE100, and why)
- **No licensed dictionary.** ASD-STE100 ships a controlled dictionary under
copyright. MOS-STE uses per-project glossaries instead (rule 11).
- **Domain terms are allowed.** MOS-STE keeps every term the work needs.
- **Reasoning prose gets structure, not amputation.** Apply the sentence and word
rules to design and doctrine writing. Allow the length a subtle argument needs.
Readable-first beats rule-strict when the two conflict.
## 4. Code — Google Style Guide
Write source code to the Google Style Guide for the language (Python, TypeScript,
Shell, Go, and so on). Match the existing file when a local convention already
exists. Keep code comments to the MOS-STE sentence and word rules.
## 5. User-facing comms — a per-user choice
Mosaic is multi-user. Different users want different comms styles. The framework
already carries the selectable setting: `communicationStyle` (`direct` |
`friendly` | `formal`, default `direct`). `mosaic init` writes it, and the
builder renders it into the generated `USER.md` "Communication Preferences"
section. This guide adds the OUTPUT meaning of each value; do not invent new
values.
The builder renders the style as prose bullets, not the token name, so match on
the leading bullet the generated `USER.md` actually contains:
| `USER.md` leading bullet | Style | User-facing output |
| ----------------------------- | ------------------ | ---------------------------------------------------------------------- |
| "Direct and concise" | `direct` (default) | MOS-STE structure — short, active, defined terms, tables for overview. |
| "Warm and conversational" | `friendly` | Warmer register. Full sentences, explain reasoning, fewer tables. |
| "Professional and structured" | `formal` | Professional and structured. Thorough, with explicit recommendations. |
This setting governs **user-facing comms only**. It does not change the
documentation standard (§3), which is always MOS-STE regardless of the value.
## 6. Enforcement
- **Now:** human review only. **No mechanical prose check exists today.** The
pre-push gate runs typecheck, lint, build, and tests; it inspects no prose.
Reviewers check output against the scope table and the MOS-STE rules by hand.
- **Future:** an MOS-STE lint check (built from the §3.1 sentence rules) and a
Google-style linter in the pre-push gate. A future linter enforces §3.1, not
§3.4 — see the note at rule 1.
+3 -31
View File
@@ -54,16 +54,6 @@ Commands:
Local-write only; a background sync
ships it (never blocks on network).
--no-sync suppresses the background ship.
--force-past-quarantine passes the #946
force flag through to store.sh consume:
the ONLY way to advance past a
QUARANTINED (dead-lettered, never
delivered) seq. The store's per-seq
step-over diagnostics are re-emitted on
stderr; no consumed-hash witness is
recorded for the quarantined entry.
Without the flag, a consume that would
cross a quarantined seq is REFUSED.
embed --upto N [--wake-id ID] [--agent A]
Print the copy-run ack line to EMBED in
a digest (does not perform the ack).
@@ -179,7 +169,7 @@ cmd_received() {
cmd_consumed() {
_need_jq
local upto='' wake_id='' do_sync="1" force="0"
local upto='' wake_id='' do_sync="1"
while [ $# -gt 0 ]; do
case "$1" in
--upto)
@@ -194,10 +184,6 @@ cmd_consumed() {
do_sync="0"
shift
;;
--force-past-quarantine)
force="1"
shift
;;
*)
echo "ack.sh consumed: unknown option '$1'" >&2
exit 2
@@ -214,25 +200,11 @@ cmd_consumed() {
# Advance consumed_seq via the store. The store enforces the CONTIGUOUS
# gapless-prefix rule and rejects a gap (cannot ack N while N-1 unconsumed).
# This is a LOCAL-WRITE cursor advance — no network.
local new_cursor store_args
store_args=(consume --upto "$upto")
if [ "$force" = "1" ]; then
store_args+=(--force-past-quarantine)
fi
if ! new_cursor="$("$STORE_SH" "${store_args[@]}" 2>&1)"; then
local new_cursor
if ! new_cursor="$("$STORE_SH" consume --upto "$upto" 2>&1)"; then
echo "ack.sh consumed: refused — $new_cursor" >&2
exit 1
fi
if [ "$force" = "1" ]; then
# #946: on the forced path the store's LOUD per-seq step-over diagnostics
# were captured together with the cursor line (2>&1 above). Re-emit them on
# OUR stderr — the loudness must survive the wrapper — and keep only the
# final line (the cursor) for the CONSUMED report below.
local cursor_line
cursor_line="$(printf '%s\n' "$new_cursor" | tail -n1)"
printf '%s\n' "$new_cursor" | sed '$d' | grep -v '^[[:space:]]*$' >&2 || true
new_cursor="$cursor_line"
fi
# Record the CONSUMED ack in the local ledger (still no network).
local record
@@ -295,25 +295,12 @@ _poll_source() {
# is interpolated into $((...)) under set -u, so a non-numeric value
# ('300s', '5m', 'abc') would be FATAL to the poll — the one thing this
# block must never be. Resolve it ONCE, validate, fall back loudly.
local now_s slack slack_ok
local now_s slack
slack="${WAKE_SNAPSHOT_TS_FUTURE_SLACK:-300}"
# NOT grep: grep is LINE-oriented, so ^...$ anchors bind per line and a
# multi-line value ($'300\n8') passes the regex yet is FATAL in $((...)).
# The case pattern matches the WHOLE string, newlines included. (snap_ts
# is immune: jq's number type-check above cannot emit an embedded newline.)
case "$slack" in
'' | *[!0-9]*) slack_ok=1 ;;
*) [ "${#slack}" -le 9 ] && slack_ok=0 || slack_ok=1 ;;
esac
if [ "$slack_ok" -ne 0 ]; then
echo "detector.sh: WAKE_SNAPSHOT_TS_FUTURE_SLACK='$slack' is not a plain non-negative integer of at most 9 digits (seconds) — falling back to 300, poll continues (#940)." >&2
if ! printf '%s' "$slack" | grep -Eq '^[0-9]{1,9}$'; then
echo "detector.sh: WAKE_SNAPSHOT_TS_FUTURE_SLACK='$slack' is not a plain non-negative integer (seconds) — falling back to 300, poll continues (#940)." >&2
slack=300
fi
# Shape validation is not radix validation: bash reads a leading zero as
# OCTAL, so '08'/'09' pass the shape check yet are FATAL in $((...)), and
# '0300' silently means 192. Force base-10 so the knob means what the
# operator wrote (safe: the case pattern above guarantees pure digits).
slack=$((10#$slack))
now_s="$(date +%s)"
if [ "$snap_ts" -gt $((now_s + slack)) ]; then
echo "detector.sh: source '$kind/$id' snapshot_ts is beyond the ${slack}s future-skew allowance (ts=$snap_ts now=$now_s) — snapshot_ts DROPPED, poll continues (#940)." >&2
+12 -92
View File
@@ -25,8 +25,8 @@
# sufficiency NEVER exempts a consequential action from its live gate.
#
# HARD LOCATORS (§2.1): every actionable claim MUST carry a precise locator
# (repo + issue#, a 40-char SHA, file:anchor, or path — #944) so re-verification
# is ONE targeted call. A missing locator = malformed ACTIONABLE entry = FAIL-LOUD.
# (repo + issue#, a 40-char SHA, or file:anchor) so re-verification is ONE
# targeted call. A missing locator = malformed ACTIONABLE entry = FAIL-LOUD.
#
# #920 (per-entry quarantine — fail-loud WITHOUT head-of-line blocking): a
# render-refused entry (actionable-tier, no hard locator) is QUARANTINED — durably
@@ -112,14 +112,6 @@ Exit codes:
diagnostic (#924/G2a) — it never wedges the whole render either.
Reconciler enumerations (locators.reconciled==true) render ORIENTATION-tier,
gate-exempt.
#946: quarantined entries are DISCLOSED in a QUARANTINED section (by
seq/class only — content stays excluded) and the embedded ack copy-run
line is CLAMPED below the lowest quarantined seq (the digest never
instructs the consumer to record a delivery that never happened; the
clamp is announced as an ACK CLAMPED note). A store-mode render also
REPLACES the store's quarantined.set (store.sh quarantine-sync) so the
consume path enforces the same clamp; --from-file/--stdin renders never
touch the set.
2 usage error.
3 jq is required but missing.
@@ -217,28 +209,13 @@ _scrub_free() {
# ---------------------------------------------------------------------------
# _has_hard_locator LOCATORS_JSON — true iff the locators object carries at least
# one PRECISE locator sufficient for one-call re-verification. BOTH locator
# vocabularies in live use are accepted (the same two _locator_line documents
# under #914b):
# forge shape (§2.1): repo + issue (issue#) | 40-hex sha | file (file:anchor)
# detector shape (#944): path
# The `path` arm was added by #944: detector.sh (A1) builds board_file locators
# as kind/id/observed_hash + path (+ snapshot_sha when adapter-attested, #940)
# — none of which the gate tested — so a class=actionable board_file entry
# could NEVER pass and every heartbeat-planning delta dead-lettered (live seqs
# 63/68). `path` mirrors `file` exactly (same one-call "re-read X" hint below;
# with an attested snapshot_sha the hint upgrades to one-call
# `git show <snapshot_sha>:<path>`). DELIBERATELY NOT ARMS: `observed_hash`
# (a content hash, not an address) and bare `snapshot_sha` without `path`
# (a path-less 40-hex would widen the gate past the board_file vocabulary —
# review-adopted criterion on #944; snapshot_sha's precision is only reachable
# THROUGH a path, so path is the address and snapshot_sha stays a refinement).
# one PRECISE locator sufficient for one-call re-verification:
# repo + issue (issue#) | 40-hex sha | file (file:anchor).
_has_hard_locator() {
jq -e '
((.repo // "") != "" and ((.issue // "") | tostring) != "")
or (((.sha // "") | test("^[0-9a-f]{40}$")))
or ((.file // "") != "")
or ((.path // "") != "")
' >/dev/null 2>&1 <<<"$1"
}
@@ -246,18 +223,16 @@ _has_hard_locator() {
# one-targeted-call re-verify hint, where available.
#
# #914b: covers BOTH locator vocabularies actually in use:
# - the HARD-locator shape (§2.1, gated by _has_hard_locator):
# - the HARD-locator shape (§2.1, gated by _has_hard_locator, unchanged):
# repo+issue | 40-hex sha | file(:anchor).
# - the shape detector.sh (A1) actually builds for a `digest`-class entry
# (see detector.sh's enqueue-locators jq filter): kind/id/observed_hash +
# whichever of repo/path/anchor/remote/branches the source def declares.
# None of kind/id/observed_hash/remote/path/branches were recognized here
# before, so a real digest-class pointer rendered a bare empty "locator:"
# line despite the entry carrying real (soft, non-hard) locator data.
# (At #914b this was display-only; #944 later promoted `path` — and ONLY
# `path` — into _has_hard_locator, since it carries the same one-call
# re-verify precision as `file`. kind/id/observed_hash/remote/branches
# and bare snapshot_sha remain soft/display-only.)
# line despite the entry carrying real (soft, non-hard) locator data. This
# is display-only: it does NOT feed _has_hard_locator, so the
# ACTIONABLE-tier hard-locator FAIL-LOUD gate is untouched.
_locator_line() {
local loc="$1" repo issue sha file anchor head parts='' reverify=''
local remote path kind id ohash branches snap_sha snap_ts
@@ -502,7 +477,7 @@ _quarantine_entry() {
return 0
fi
fi
echo "digest.sh: FAIL-LOUD QUARANTINE (#920) — malformed ACTIONABLE entry at observed_seq=$seq has no §2.1 hard locator (repo+issue# / 40-hex sha / file:anchor / path). DEAD-LETTERED to $dlq and EXCLUDED from this digest; the rest of the cumulative set still renders (no head-of-line block). Re-feed the source with a valid hard locator." >&2
echo "digest.sh: FAIL-LOUD QUARANTINE (#920) — malformed ACTIONABLE entry at observed_seq=$seq has no §2.1 hard locator (repo+issue#/40-char SHA/file:anchor). DEAD-LETTERED to $dlq and EXCLUDED from this digest; the rest of the cumulative set still renders (no head-of-line block). Re-feed the source with a valid hard locator." >&2
# #924 (G2a): route the SAME per-entry alarm off-host too, deduped by
# observed_seq so a still-dead-lettered entry re-drained every tick is
# alarmed off-host EXACTLY ONCE (never once per re-render).
@@ -552,7 +527,7 @@ cmd_render() {
# #920 head-of-line-blocking defect that exit-4'd the entire cumulative-state
# drain). Reconciler enumerations (locators.reconciled==true) are ORIENTATION-
# tier and gate-exempt, so they pass straight through to the deliverable set.
local line loc seq pending_ok='' quarantined=0 q_seqs='' q_disclose=''
local line loc seq pending_ok='' quarantined=0
while IFS= read -r line; do
[ -n "$line" ] || continue
printf '%s' "$line" | jq -e . >/dev/null 2>&1 || continue
@@ -562,17 +537,6 @@ cmd_render() {
seq="$(jq -r '.observed_seq // "?"' <<<"$line")"
_quarantine_entry "$line" "$seq"
quarantined=$((quarantined + 1))
# #946: collect the quarantined identity for DISCLOSURE + the ack
# CLAMP. Disclosure is by durable identity (observed_seq) + class ONLY:
# this entry failed the locator gate, so its content is exactly what
# this digest refuses to re-inject (the exclusion property Q1/Q4
# assert) — the consumer re-verifies via the dead-letter ledger, never
# via this line.
case "$seq" in
'' | *[!0-9]*) : ;; # an unnumbered entry cannot clamp the numeric cursor
*) q_seqs="$q_seqs$seq"$'\n' ;;
esac
q_disclose="$q_disclose * seq $seq [$(_scrub_inline "$(jq -r '.class // "actionable"' <<<"$line")")] HELD — dead-lettered (no §2.1 hard locator); content withheld, NOT delivered."$'\n'
continue
fi
fi
@@ -582,33 +546,6 @@ cmd_render() {
pending="$(printf '%s' "$pending_ok" | grep -v '^[[:space:]]*$' || true)"
depth="$(printf '%s\n' "$pending" | grep -c . || true)"
# --- #946: quarantine truth-sync + ack clamp ------------------------------
# (1) SYNC: an AUTHORITATIVE full-set render (src=store) REPLACES the store's
# quarantined.set with THIS render's quarantined seqs (possibly none — an
# empty replace IS the #944 recovery: once the gate is fixed and everything
# renders, the stale set clears and the store-side clamp self-heals). A
# foreign-data render (--from-file/--stdin) must NEVER rewrite lane truth.
if [ "$src" = "store" ]; then
if ! printf '%s' "$q_seqs" | "$STORE_SH" quarantine-sync; then
echo "digest.sh: WARN (#946) — store.sh quarantine-sync FAILED; the store-side consume clamp may be stale for this lane (the clamped ack line rendered below is still correct)." >&2
fi
fi
# (2) CLAMP: the embedded ack may advance AT MOST to just below the LOWEST
# quarantined seq — consume requires a contiguous prefix, so one held seq
# caps everything above it. With nothing quarantined this is the observed
# cursor unchanged. Render-local on purpose: it protects the copy-run line in
# EVERY mode, including hermetic --from-file renders.
local ack_upto="$observed" min_q='' qs q_list=''
while IFS= read -r qs; do
[ -n "$qs" ] || continue
if [ -z "$min_q" ] || [ "$qs" -lt "$min_q" ]; then min_q="$qs"; fi
done <<<"$q_seqs"
if [ -n "$min_q" ] && [ "$((min_q - 1))" -lt "$ack_upto" ]; then
ack_upto=$((min_q - 1))
fi
[ "$ack_upto" -ge 0 ] || ack_upto=0
q_list="$(printf '%s' "$q_seqs" | tr '\n' ' ' | sed -e 's/[[:space:]]*$//')"
# --- render (all validated) ----------------------------------------------
local n_actionable=0
{
@@ -702,16 +639,6 @@ cmd_render() {
printf '%s\n' "$hbody"
fi
# #946: QUARANTINED disclosure — a held entry must be VISIBLE in the digest
# it was held from (five successive live digests each silently stepped the
# consumer past buried seq 68). Disclosure is by seq/class ONLY; the
# entry's content already failed the locator gate and stays EXCLUDED.
if [ -n "$q_disclose" ]; then
printf '\n-- QUARANTINED (dead-lettered; HELD — NOT delivered; the ack below does NOT cover these) --\n'
printf '%s' "$q_disclose"
printf ' disposition: see %s/dead-letter.jsonl — fix the source locator (re-delivery is automatic once the entry passes the gate), or step past EXPLICITLY with ack.sh consumed --force-past-quarantine.\n' "$STATE_DIR"
fi
# Embedded ack copy-run line (W2). CONSUMED is a consumer act; this is the
# exact local-write line the consumer runs after durable capture.
#
@@ -726,19 +653,12 @@ cmd_render() {
# itself was env-less (agent=="default"), baking "default" is no worse than
# today — the fix wins the common case where WAKE_AGENT was set at render.
printf '\n-- ACK (copy-run; local-write only, never blocks on network) --\n'
# #946: the embedded --upto is the CLAMPED cursor (ack_upto), never the raw
# observed cursor while a quarantined seq sits inside (consumed, observed] —
# the copy-run line itself must not instruct the consumer to record
# deliveries that never happened. The clamp is disclosed loudly.
if [ "$ack_upto" -ne "$observed" ]; then
printf '# ACK CLAMPED (#946): embedding --upto %s, not observed_seq %s — quarantined seq(s) %s were dead-lettered and NEVER delivered; an ordinary ack cannot step past them. Only ack.sh consumed ... --force-past-quarantine (loud) can.\n' "$ack_upto" "$observed" "$q_list"
fi
local ack_line agent_scrubbed
agent_scrubbed="$(_scrub_inline "$agent")"
if [ -n "$wake_id" ]; then
ack_line="$("$ACK_SH" embed --upto "$ack_upto" --agent "$agent_scrubbed" --wake-id "$wake_id" 2>/dev/null || true)"
ack_line="$("$ACK_SH" embed --upto "$observed" --agent "$agent_scrubbed" --wake-id "$wake_id" 2>/dev/null || true)"
else
ack_line="$("$ACK_SH" embed --upto "$ack_upto" --agent "$agent_scrubbed" 2>/dev/null || true)"
ack_line="$("$ACK_SH" embed --upto "$observed" --agent "$agent_scrubbed" 2>/dev/null || true)"
fi
printf '%s\n' "${ack_line:-# ack unavailable}"
} | _redact_secrets
@@ -276,21 +276,10 @@
# 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
# gets the same discipline (#941 §2 round 2): validated
# ^[0-9]{1,9}$, 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
# one must never invert the guard into deny-all. 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
@@ -306,76 +295,8 @@
# 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.
# 0.6.15 #946 the digest's embedded ack watermark covered quarantined
# entries: quarantine is a render-time filter (0.6.14), so the
# suggested `ack.sh consumed --upto <observed_seq>` stepped the
# cursor PAST dead-lettered seqs and _record_last_consumed then
# wrote consumed-hash witness rows for deliveries that never
# happened (live: mos-dt seq 68 buried under five digests;
# Finding A: a false 9d0f639f…@63 witness row). Fix — disclose
# AND clamp: (1) the rendered digest gains a QUARANTINED section
# (seq + class + HELD only; ids/locators stay withheld,
# preserving the exclusion property) and the embedded ack is
# clamped to min(observed_seq, min quarantined seq 1);
# (2) store.sh consume REFUSES to cross an unconsumed
# quarantined seq — `--force-past-quarantine` (plumbed through
# ack.sh consumed) is the ONLY way past, loud per-seq on stderr,
# and even the forced path never writes a consumed-hash witness
# for a quarantined seq; (3) render --from-store syncs the
# store-owned quarantined.set via new `store.sh quarantine-sync`
# (full-replace, so a gate fix self-heals stale quarantine;
# --from-file/--stdin never touch the set); (4) new
# `store.sh quarantine-audit [--repair]` sweeps consumed-hashes
# for rows provably contradicted by the dead-letter ledger
# (report exits 1; --repair removes only provably-false rows;
# the ledger itself is history and is never modified; rows whose
# dead-letter evidence was pruned are unprovable and untouched).
# Changed: store.sh, digest.sh, ack.sh
# (+ test-wake-store-ack.sh T13-T16,
# test-wake-digest-quarantine.sh Q12-Q16).
component=wake
version=0.6.15
version=0.6.12
# Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version
+6 -208
View File
@@ -77,42 +77,11 @@ Commands:
the actual paste is out of scope.
If --require-idle-cmd is given and it
exits non-zero, emit nothing (not idle).
consume --upto N [--force-past-quarantine]
Advance consumed_seq over the contiguous
consume --upto N Advance consumed_seq over the contiguous
gapless prefix <=N; drop consumed
entries. Rejects a gap (cannot ack N
while N-1 is unconsumed). Cumulative &
idempotent. REFUSES to advance past a
QUARANTINED seq (#946): a quarantined
entry was dead-lettered at render and
never delivered in any digest, so an
ordinary ack may not record it consumed.
--force-past-quarantine is the ONLY way
past — loud per stepped-over seq, and
even then NO consumed-hash witness is
recorded for the quarantined entry.
quarantine-sync REPLACE the store-owned quarantined.set
with the observed_seqs read from stdin
(one per line; empty input CLEARS).
Called by digest.sh after each
authoritative store render — the set is
re-DERIVED per render, never accumulated,
so a fixed locator gate self-heals the
consume clamp (#944 recovery).
quarantine-audit [--repair] Report consumed-hashes rows that are
PROVABLY FALSE: the row matches a
dead-letter ledger entry on
(kind,id,observed_seq,observed_hash) at
or below consumed_seq — i.e. the recorded
consumption was of a quarantined,
never-delivered entry (#946 Finding A).
Report-only by default (exit 1 when any
found); --repair removes exactly those
rows (atomic, loud). The dead-letter
ledger itself is NEVER modified (it is
history). Rows whose dead-letter evidence
was pruned are NOT provable and are
never touched.
idempotent.
cursors Print observed_seq / consumed_seq / depth.
Environment:
@@ -385,20 +354,10 @@ cmd_drain() {
# (the reconciler re-enumerates the consumed state once — no lost obligation),
# never a failed consume.
_record_last_consumed() {
local upto="$1" existing new_records merged qjson
local upto="$1" existing new_records merged
[ -f "$STATE_DIR/pending.jsonl" ] || return 0
# #946: seqs in quarantined.set are EXCLUDED from the record — the trust
# boundary above says "existence implies durably enqueued+CONSUMED", but a
# quarantined entry was dead-lettered at render and NEVER delivered, so a row
# for it would witness a delivery that never happened. This holds even on the
# FORCED step-over path: the reconciler re-enumerating the state once is
# safe-but-noisy; a false witness silences it forever.
qjson="$(jq -nR -c '[inputs | select(length > 0) | tonumber? // empty]' "$STATE_DIR/quarantined.set" 2>/dev/null || true)"
[ -n "$qjson" ] || qjson='[]'
new_records="$(jq -c --argjson upto "$upto" --argjson quarantined "$qjson" '
(.observed_seq // -1) as $seq
| select($seq <= $upto)
| select(($quarantined | index($seq)) == null)
new_records="$(jq -c --argjson upto "$upto" '
select((.observed_seq // -1) <= $upto)
| select((.locators.kind // "") != "" and (.locators.id // "") != "" and (.locators.observed_hash // "") != "")
| {kind:.locators.kind, id:.locators.id, observed_hash:.locators.observed_hash, observed_seq:.observed_seq}
' "$STATE_DIR/pending.jsonl" 2>/dev/null || true)"
@@ -413,17 +372,13 @@ _record_last_consumed() {
}
cmd_consume() {
local upto='' force=0
local upto=''
while [ $# -gt 0 ]; do
case "$1" in
--upto)
upto="${2:-}"
shift 2
;;
--force-past-quarantine)
force=1
shift
;;
*)
echo "store.sh consume: unknown option '$1'" >&2
exit 2
@@ -471,33 +426,6 @@ cmd_consume() {
k=$((k + 1))
done
# --- #946 quarantine CLAMP -------------------------------------------------
# A quarantined seq was DEAD-LETTERED at render (no §2.1 hard locator): it was
# never delivered in any digest, so advancing consumed_seq past it would record
# consumption of an entry the consumer has never seen. The ordinary path
# REFUSES; --force-past-quarantine is the ONLY way past, and it is loud per
# stepped-over seq. digest.sh re-derives the set at each authoritative store
# render (quarantine-sync REPLACE), so a fixed locator gate self-heals this
# clamp without operator action.
local qfile="$STATE_DIR/quarantined.set" blocked='' q
if [ -s "$qfile" ]; then
while IFS= read -r q; do
case "$q" in '' | *[!0-9]*) continue ;; esac
if [ "$q" -gt "$consumed" ] && [ "$q" -le "$upto" ]; then
blocked="$blocked $q"
fi
done <"$qfile"
fi
if [ -n "$blocked" ]; then
if [ "$force" -eq 0 ]; then
echo "store.sh consume: REFUSED (#946 quarantine clamp) — quarantined seq(s):$blocked inside (consumed_seq=$consumed, upto=$upto] were dead-lettered at render and NEVER delivered in any digest. Advancing past them would record consumption of entries the consumer has never seen. Disposition them (see $STATE_DIR/dead-letter.jsonl) or step over EXPLICITLY with --force-past-quarantine." >&2
exit 1
fi
for q in $blocked; do
echo "store.sh consume: FORCED PAST QUARANTINE (#946) — stepping consumed_seq over quarantined seq $q (dead-lettered, NEVER delivered). No consumed-hash witness is recorded for it; the obligation stays visible ONLY in $STATE_DIR/dead-letter.jsonl." >&2
done
fi
# #932: record the last-consumed observed_hash per (kind,id) BEFORE the pending
# prefix is dropped (this reads the entries about to be truncated), so the
# reconciler can recognise an already-consumed state as ACCOUNTED instead of
@@ -511,137 +439,9 @@ cmd_consume() {
awk -v c="$upto" 'NF && $1+0 > c' "$STATE_DIR/observed.set" 2>/dev/null |
_atomic_write "$STATE_DIR/observed.set" || true
printf '%s' "$upto" | _atomic_write "$STATE_DIR/consumed_seq"
# #946: on a forced step-over, PRUNE the stepped-over seqs from quarantined.set
# (they are inside the consumed prefix now; a stale entry would re-refuse the
# next consume forever). Mirrors the observed.set prune idiom above.
if [ -n "$blocked" ]; then
awk -v c="$upto" 'NF && $1+0 > c' "$qfile" 2>/dev/null |
_atomic_write "$qfile" || true
fi
echo "$upto"
}
# cmd_quarantine_sync — #946: REPLACE the store-owned quarantined.set with the
# observed_seqs read from stdin (one per line). Called by digest.sh after each
# AUTHORITATIVE full-set render (src=store): the set is re-DERIVED per render,
# never accumulated, so a fixed locator gate self-heals the consume clamp (the
# #944 recovery case — a cumulative-forever set would keep refusing acks on
# entries that now render). Empty input CLEARS the set. Foreign-data renders
# (--from-file/--stdin) never call this. Atomic write; invalid input is refused
# loudly with the set left untouched.
cmd_quarantine_sync() {
[ $# -eq 0 ] || {
echo "store.sh quarantine-sync: takes no options (observed_seqs on stdin, one per line)" >&2
exit 2
}
local seq list=''
while IFS= read -r seq; do
[ -n "$seq" ] || continue
case "$seq" in
*[!0-9]*)
echo "store.sh quarantine-sync: invalid observed_seq '$seq' — one non-negative integer per line; set left untouched" >&2
exit 2
;;
esac
list="$list$seq"$'\n'
done
_wake_init_dir "$STATE_DIR"
if ! { printf '%s' "$list" | grep -v '^[[:space:]]*$' || true; } | sort -n | uniq | _atomic_write "$STATE_DIR/quarantined.set"; then
echo "store.sh quarantine-sync: quarantined.set write FAILED — the consume clamp may be stale for this lane" >&2
exit 1
fi
}
# cmd_quarantine_audit — #946 Finding A: the pre-#946 consume recorded
# consumed-hash rows for QUARANTINED (never-delivered) entries — false
# witnesses that silence the reconciler for keys whose only "consumption" was a
# dead-lettered entry (live: safe by population only where the key re-emitted
# and a later seq won the per-key max_by merge; keys that never recurred keep
# the false row forever).
#
# A row is PROVABLY FALSE iff the dead-letter ledger contains an entry matching
# it on (kind, id, observed_seq, observed_hash) AND row.observed_seq <=
# consumed_seq: the per-key max_by merge means the surviving row's provenance IS
# that quarantined entry (a healed row differs in seq/hash and never matches).
# PROVABILITY BOUND: a row whose dead-letter evidence was pruned/rotated away is
# NOT provable and is never touched — this audit only ever removes what the
# ledger can convict. The dead-letter ledger itself is history and is NEVER
# modified here.
cmd_quarantine_audit() {
local repair=0
while [ $# -gt 0 ]; do
case "$1" in
--repair)
repair=1
shift
;;
*)
echo "store.sh quarantine-audit: unknown option '$1'" >&2
exit 2
;;
esac
done
_need_jq
_wake_init_dir "$STATE_DIR"
local rec="$STATE_DIR/consumed-hashes.jsonl" dlf="$STATE_DIR/dead-letter.jsonl"
if [ ! -s "$rec" ]; then
echo "store.sh quarantine-audit: OK — no consumed-hashes record to audit"
return 0
fi
local dl
dl="$(jq -s -c '[.[] | {kind: (.locators.kind // ""), id: ((.locators.id // "") | tostring), observed_seq: (.observed_seq // -1), observed_hash: (.locators.observed_hash // "")}]' "$dlf" 2>/dev/null || true)"
[ -n "$dl" ] || dl='[]'
local consumed
consumed="$(_wake_read_int "$STATE_DIR/consumed_seq" 0)"
local false_rows
false_rows="$(jq -c --argjson dl "$dl" --argjson consumed "$consumed" '
. as $row
| select(($row.observed_seq // -1) <= $consumed)
| select(($dl | map(select(
.kind == ($row.kind // "")
and .id == (($row.id // "") | tostring)
and .observed_seq == ($row.observed_seq // -1)
and .observed_hash == ($row.observed_hash // "")
)) | length) > 0)
' "$rec" 2>/dev/null || true)"
if [ -z "$false_rows" ]; then
echo "store.sh quarantine-audit: OK — no provably-false consumed-hash rows (rows without surviving dead-letter evidence are not provable and were not judged)"
return 0
fi
local n row
n="$(printf '%s\n' "$false_rows" | grep -c . || true)"
while IFS= read -r row; do
[ -n "$row" ] || continue
if [ "$repair" -eq 1 ]; then
echo "store.sh quarantine-audit: REPAIR — removing FALSE WITNESS row $row (matches a dead-lettered, never-delivered entry at/below consumed_seq=$consumed)" >&2
else
echo "FALSE WITNESS — consumed-hashes row $row matches a dead-lettered, never-delivered entry at/below consumed_seq=$consumed (the recorded consumption never happened)"
fi
done <<<"$false_rows"
if [ "$repair" -eq 0 ]; then
echo "store.sh quarantine-audit: $n provably-false row(s) found — run with --repair to remove exactly these rows"
return 1
fi
local kept
kept="$(jq -c --argjson dl "$dl" --argjson consumed "$consumed" '
. as $row
| select(
(($row.observed_seq // -1) > $consumed)
or (($dl | map(select(
.kind == ($row.kind // "")
and .id == (($row.id // "") | tostring)
and .observed_seq == ($row.observed_seq // -1)
and .observed_hash == ($row.observed_hash // "")
)) | length) == 0)
)
' "$rec" 2>/dev/null || true)"
if ! { printf '%s\n' "$kept" | grep -v '^[[:space:]]*$' || true; } | _atomic_write "$rec"; then
echo "store.sh quarantine-audit: consumed-hashes rewrite FAILED — record left untouched" >&2
exit 1
fi
echo "store.sh quarantine-audit: repaired — removed $n provably-false row(s); dead-letter ledger untouched (history)"
}
cmd_cursors() {
_wake_init_dir "$STATE_DIR"
local observed consumed depth
@@ -663,8 +463,6 @@ main() {
enqueue) cmd_enqueue "$@" ;;
drain) cmd_drain "$@" ;;
consume) cmd_consume "$@" ;;
quarantine-sync) cmd_quarantine_sync "$@" ;;
quarantine-audit) cmd_quarantine_audit "$@" ;;
cursors) cmd_cursors "$@" ;;
-h | --help | help) usage ;;
*)
@@ -644,44 +644,6 @@ EOF
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: negative slack must NOT invert the guard into deny-all [$entry]"
# (c2) MULTI-LINE knobs — grep's ^...$ anchors bind PER LINE, so a value with
# an embedded newline ($'300\n8') passed the old regex whole yet is FATAL in
# \$((...)) ('error token is "8"'; $'300\nabc' dies as unbound variable). The
# case pattern matches the WHOLE string: both must fall back loudly, keep the ts.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-CC2\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\n8' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\n8) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: valid metadata must SURVIVE a multi-line knob [$entry]"
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-CC3\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\nabc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\nabc) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line non-numeric slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: valid metadata must SURVIVE a multi-line non-numeric knob [$entry]"
# (d2-pre) ZERO-PADDED knobs — shape-valid, radix-hostile. '08' passes the
# regex but is fatal octal in \$((...)) without the 10# normalization; '0300'
# silently means 192 (octal), so a ts +250s ahead would be WRONGLY dropped.
# With 10#: '08' means 8 and survives; '0300' means 300 and the +250s ts is KEPT.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-DD2\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='08' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='08' (octal-fatal without 10#) must NEVER fail the poll (rc=$rc) [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: SLACK='08' with a current ts must keep the metadata [$entry]"
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(( $(date +%s) + 250 ))" >"$stub/repo_r1.meta"
printf 'SHA-DD3\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0300' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='0300' must not fail the poll (rc=$rc)"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: SLACK='0300' must mean 300 (decimal), so a +250s ts is KEPT — octal 192 would have dropped it [$entry]"
# (d) a VALID knob is still honored: slack=0 with a ts 60s ahead -> future-skew drop.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(( $(date +%s) + 60 ))" >"$stub/repo_r1.meta"
printf 'SHA-EEE\n' >"$stub/repo_r1"
@@ -691,7 +653,7 @@ EOF
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|| fail_msg "D13: valid SLACK=0 must drop the future ts but keep the sha [$entry]"
[ "$(det_seq)" = "8" ] || fail_msg "D13: all eight real deltas must still have enqueued, got seq $(det_seq)"
[ "$(det_seq)" = "4" ] || fail_msg "D13: all four real deltas must still have enqueued, got seq $(det_seq)"
) && ok
echo
@@ -4,20 +4,12 @@
# blocking) + reconciler ENUMERATIONS render ORIENTATION-tier.
#
# Each test asserts ONE invariant and goes RED against the pre-#920 digest.sh:
# Q1 (a) QUARANTINE + REST-DELIVERS: a malformed `actionable` carrying NO hard
# locator ({kind,id,observed_hash} — a content hash but no ADDRESS, no
# reconciled marker) is DEAD-LETTERED + alarmed AND EXCLUDED, while a
# clean valid sibling in the SAME drain still renders (exit 0).
# Q1 (a) QUARANTINE + REST-DELIVERS: a malformed `actionable` carrying the live
# pilot's EXACT locator shape {kind,id,path,observed_hash} (no hard
# locator, no reconciled marker) is DEAD-LETTERED + alarmed AND EXCLUDED,
# while a clean valid sibling in the SAME drain still renders (exit 0).
# RED baseline: the old whole-digest exit-4 delivered NOTHING (the live
# head-of-line-blocking wedge). (#920 FIX1)
# [#944 AMENDMENT: at #920 this fixture pinned the live pilot's shape
# {kind,id,path,observed_hash} as the malformed case — ratifying a gate
# the detector's own locators could never satisfy (every actionable
# heartbeat-planning delta dead-lettered; live seqs 63/68). #944 amends
# that ruling: `path` is now a hard-locator arm — and ONLY path;
# bare snapshot_sha is deliberately NOT an arm, Q11(d) asserts it
# still quarantines — so the quarantine fixtures here and in Q6-Q9
# use genuinely ADDRESS-FREE shapes instead.]
# Q2 (b) ENUM-AS-ORIENTATION: a reconciler enumeration (locators.reconciled==
# true) renders as an ORIENTATION-tier pointer and does NOT exit-4 / is
# NOT quarantined. RED baseline: reconciled `actionable` + soft locators
@@ -61,55 +53,8 @@
# (Q6/Q7/Q8 all see 0 captured alarms) and no "FAIL LOUD ... alarm sink"
# diagnostic exists to fire (Q9).
#
# #944 (unsatisfiable-gate fix): _has_hard_locator gains the `path` arm — and
# ONLY that arm — covering the detector-built board_file vocabulary.
# Q11 POSITIVE CONTROL + RETAINED NEGATIVES, one drain: (a) the live
# seq-68 entry VERBATIM (the exact production entry that dead-lettered
# under the unsatisfiable gate: kind/id/observed_hash + path +
# snapshot_sha + snapshot_ts, class=actionable, as detector.sh
# actually emits it — NOT a hand-built dict) must RENDER as a
# CLAIM@seq with the one-call `git show <snapshot_sha>:<path>`
# re-verify hint — the assertion is the rendered claim, not merely
# the predicate returning true; (b) a path-only sibling (no snapshot
# attestation — a pre-#940 adapter or a dropped attestation) must
# ALSO render, with the "re-read <path>" hint; (c) an address-free
# sibling ({kind,id,observed_hash}) must STILL quarantine + route its
# own alarm — observed_hash is a content hash, not an address; (d)
# bare path-less snapshot_sha siblings must ALSO still quarantine —
# the widened gate must not widen PAST the board_file vocabulary
# (review-adopted criterion) — asserted at THREE lengths (7-char
# abbreviation, 40-hex, 64-char sha-256) spanning the detector's
# actual attestation validation ^[0-9a-f]{7,64}$ (detector.sh), so
# the assertion distinguishes "no snapshot_sha arm" from "an arm
# present but length-gated". RED baseline: pre-#944
# digest.sh dead-letters (a) and (b) — an assertion nobody has seen
# succeed is as unproven as one nobody has seen fail.
#
# #946 (ack watermark passes quarantined entries): the rendered digest embedded
# `ack.sh consumed --upto <observed>` even when entries in (consumed, observed]
# were quarantined — the copy-run line itself instructed the consumer to record
# deliveries that never happened (live: five successive digests each stepping
# the consumer past buried seq 68). Fix = DISCLOSE + CLAMP + force-only-past:
# Q12 disclosure (by seq — content stays EXCLUDED per Q1/Q4) + the
# embedded ack CLAMPED below the lowest quarantined seq, hermetic
# --from-file; a foreign-data render must NOT write the store's
# quarantined.set.
# Q13 nothing quarantined -> unclamped ack at the observed cursor; no
# disclosure section, no clamp note.
# Q14 store-mode render SYNCS quarantined.set (REPLACE) -> the store's
# ordinary consume path refuses past the held seq END-TO-END.
# Q15 gate-fix RECOVERY: a stale quarantined.set is REPLACED (cleared) by
# a clean store-mode render — the clamp self-heals (#944 recovery
# invariant; a cumulative-forever set would keep blocking acks on
# entries a fixed gate now renders).
# Q16 (guard, green-by-design) Q2's ENUM-B fixture must STAY address-free
# so the reconciled exemption remains load-bearing at the gate
# (#944 F1); goes RED only if the fixture regresses.
#
# Hermetic: feeds controlled JSONL via `digest.sh render --from-file` — NO store,
# NO network, NO openssl (so it runs identically under the CI openssl-mask).
# (Q14/Q15 are the intentional exception: the #946 store sync is store-mode-only
# behavior, so they drive store.sh enqueue/consume against a temp state home.)
#
# Each test runs in its own (..) subshell for env isolation; the per-subshell
# WAKE_STATE_HOME export is intentional (mirrors test-wake-reconcile.sh).
@@ -160,16 +105,15 @@ fresh_home() {
# dlq HOME — the dead-letter path for the default agent under HOME.
dlq() { printf '%s/default/dead-letter.jsonl' "$1"; }
echo "== Q1 (a): malformed address-free {kind,id,observed_hash} QUARANTINES; clean sibling STILL delivers =="
echo "== Q1 (a): malformed {kind,id,path,observed_hash} QUARANTINES; clean sibling STILL delivers =="
(
home="$(fresh_home q1)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
f="$TMP_ROOT/q1.jsonl"
# An ADDRESS-FREE malformed shape (no reconciled marker) + a clean sibling.
# [#944: the original fixture carried `path`, which is now a hard-locator arm.]
# The live pilot's EXACT malformed shape (no reconciled marker) + a clean sibling.
{
printf '%s\n' '{"observed_seq":1,"class":"actionable","locators":{"kind":"repo","id":"MALFORMED-Q","observed_hash":"deadbeef"},"emit_ts":1}'
printf '%s\n' '{"observed_seq":1,"class":"actionable","locators":{"kind":"repo","id":"MALFORMED-Q","path":"docs/x.md","observed_hash":"deadbeef"},"emit_ts":1}'
printf '{"observed_seq":2,"class":"actionable","locators":{"sha":"%s","file":"src/a.ts"},"emit_ts":1}\n' "$SHA40"
} >"$f"
err="$TMP_ROOT/q1.err"
@@ -191,11 +135,7 @@ echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-ti
unset WAKE_AGENT
f="$TMP_ROOT/q2.jsonl"
# A reconciler enumeration: store class actionable (unchanged) + reconciled marker.
# ADDRESS-FREE on purpose (#944 F1): no path/file/sha/repo+issue — the reconciled
# exemption must be the ONLY thing keeping this entry out of quarantine, so the
# exemption is proven load-bearing AT THE GATE (mutation-killable), not merely at
# the tier label. (Q3's ENUM-C* stay path-bearing: reconciled + valid-locator mix.)
printf '%s\n' '{"observed_seq":5,"class":"actionable","locators":{"kind":"repo","id":"ENUM-B","observed_hash":"cafe1234","reconciled":true},"emit_ts":1}' >"$f"
printf '%s\n' '{"observed_seq":5,"class":"actionable","locators":{"kind":"repo","id":"ENUM-B","path":"BOARD.md","observed_hash":"cafe1234","reconciled":true},"emit_ts":1}' >"$f"
err="$TMP_ROOT/q2.err"
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$?
@@ -269,7 +209,7 @@ echo "== Q6 (a): dead-lettered entry routes EXACTLY ONE off-host alarm (payload
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
f="$TMP_ROOT/q6.jsonl"
printf '%s\n' '{"observed_seq":21,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q6","observed_hash":"aaaa"},"emit_ts":1}' >"$f"
printf '%s\n' '{"observed_seq":21,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q6","path":"x.md","observed_hash":"aaaa"},"emit_ts":1}' >"$f"
ALARM_OUT="$TMP_ROOT/q6.alarm.jsonl"
export ALARM_OUT
: >"$ALARM_OUT"
@@ -292,7 +232,7 @@ echo "== Q7 (b): re-draining the SAME still-dead-lettered entry N times routes Z
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
f="$TMP_ROOT/q7.jsonl"
printf '%s\n' '{"observed_seq":22,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q7","observed_hash":"bbbb"},"emit_ts":1}' >"$f"
printf '%s\n' '{"observed_seq":22,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q7","path":"y.md","observed_hash":"bbbb"},"emit_ts":1}' >"$f"
ALARM_OUT="$TMP_ROOT/q7.alarm.jsonl"
export ALARM_OUT
: >"$ALARM_OUT"
@@ -312,8 +252,8 @@ echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (de
unset WAKE_AGENT
f1="$TMP_ROOT/q8a.jsonl"
f2="$TMP_ROOT/q8b.jsonl"
printf '%s\n' '{"observed_seq":31,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8A","observed_hash":"c1"},"emit_ts":1}' >"$f1"
printf '%s\n' '{"observed_seq":32,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8B","observed_hash":"c2"},"emit_ts":1}' >"$f2"
printf '%s\n' '{"observed_seq":31,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8A","path":"a.md","observed_hash":"c1"},"emit_ts":1}' >"$f1"
printf '%s\n' '{"observed_seq":32,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8B","path":"b.md","observed_hash":"c2"},"emit_ts":1}' >"$f2"
ALARM_OUT="$TMP_ROOT/q8.alarm.jsonl"
export ALARM_OUT
: >"$ALARM_OUT"
@@ -333,7 +273,7 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
f="$TMP_ROOT/q9.jsonl"
printf '%s\n' '{"observed_seq":41,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q9","observed_hash":"dddd"},"emit_ts":1}' >"$f"
printf '%s\n' '{"observed_seq":41,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q9","path":"z.md","observed_hash":"dddd"},"emit_ts":1}' >"$f"
# (a) UNCONFIGURED alarm sink.
unset WAKE_ALARM_SINK_CMD
err_a="$TMP_ROOT/q9a.err"
@@ -397,185 +337,6 @@ echo "== Q10: snapshot metadata (#940) — snapshot_sha/snapshot_ts render on th
true
) && ok
echo "== Q11 (#944): REAL detector-shape actionable RENDERS as CLAIM@seq; address-free + bare-snapshot_sha siblings STILL quarantine =="
(
home="$(fresh_home q11)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
f="$TMP_ROOT/q11.jsonl"
{
# (a) the LIVE seq-68 entry VERBATIM — the exact production entry that
# dead-lettered on the mos-dt lane under the unsatisfiable gate
# (dragon-lin dead-letter.jsonl, 2026-07-30). Detector-emitted shape,
# not a hand-built dict.
printf '%s\n' '{"observed_seq":68,"locators":{"kind":"board_file","id":"heartbeat-planning","observed_hash":"2e85f2474001961e920976a91981eca5bb86a5ff0df044e082a1e1f2dc7493b5","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce","snapshot_ts":1785414523,"path":"docs/scratchpads/heartbeat-planning"},"class":"actionable","emit_ts":1785415057,"hmac":""}'
# (b) same vocabulary WITHOUT snapshot attestation (pre-#940 adapter or
# dropped-as-malformed attestation) — must pass via the path arm alone.
printf '%s\n' '{"observed_seq":69,"class":"actionable","locators":{"kind":"board_file","id":"PILOT-LOCAL","observed_hash":"abcd1234","path":"BOARD.md"},"emit_ts":2}'
# (c) ADDRESS-FREE — the retained negative: a content hash is not an address.
printf '%s\n' '{"observed_seq":70,"class":"actionable","locators":{"kind":"board_file","id":"ADDR-FREE","observed_hash":"ffff0000"},"emit_ts":2}'
# (d) bare path-less snapshot_sha — must NOT pass: the widened gate must
# not widen past the board_file vocabulary. Asserted at all three
# lengths the detector's attestation validation ^[0-9a-f]{7,64}$
# admits: a 40-hex sha-1, a 7-char abbreviation, a 64-char sha-256.
# One length alone cannot distinguish "no arm" from "arm present but
# length-gated" (enumeration finding E1).
printf '%s\n' '{"observed_seq":71,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY-40","observed_hash":"eeee1111","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce"},"emit_ts":2}'
printf '%s\n' '{"observed_seq":72,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY-7","observed_hash":"eeee2222","snapshot_sha":"55d4909"},"emit_ts":2}'
printf '%s\n' '{"observed_seq":73,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY-64","observed_hash":"eeee3333","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce55d4909569d2b5fbccfec49e"},"emit_ts":2}'
} >"$f"
ALARM_OUT="$TMP_ROOT/q11.alarm.jsonl"
export ALARM_OUT
: >"$ALARM_OUT"
export WAKE_ALARM_SINK_CMD="$CAPTURE_ALARM"
err="$TMP_ROOT/q11.err"
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q11: render must exit 0, got rc=$rc"
# (a) POSITIVE: the live entry RENDERS as an actionable claim (not merely
# passes the predicate) with the one-call git-show re-verify hint.
printf '%s' "$out" | grep -q 'seq 68 — CLAIM@seq' || fail_msg "Q11a: the live seq-68 detector-shape entry must RENDER as CLAIM@seq (positive control)"
printf '%s' "$out" | grep -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
|| fail_msg "Q11a: the rendered claim must carry the one-call re-verify hint git show <snapshot_sha>:<path>"
# (b) POSITIVE: path arm alone suffices; hint degrades to one-call re-read.
printf '%s' "$out" | grep -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
printf '%s' "$out" | grep -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint"
n_claims="$(printf '%s\n' "$out" | grep -c 'CLAIM@seq' || true)"
[ "$n_claims" = "2" ] || fail_msg "Q11: EXACTLY the two valid entries must render as CLAIM@seq (got $n_claims)"
# (c)+(d) NEGATIVES retained: both quarantine, each with its OWN alarm.
printf '%s' "$out" | grep -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
printf '%s' "$out" | grep -q 'SNAP-ONLY' && fail_msg "Q11d: no bare path-less snapshot_sha entry may appear in the digest (gate must not widen past board_file)"
grep -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do
grep -q "$snap_id" "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry ($snap_id) must be DEAD-LETTERED"
done
grep -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
grep -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
n_alarms="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
[ "$n_alarms" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
grep -q '"observed_seq":70' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11c: seq 70's own alarm must be present"
for snap_seq in 71 72 73; do
grep -q "\"observed_seq\":$snap_seq" "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq $snap_seq's own alarm must be present"
done
true
) && ok
echo "== Q12 (#946): quarantined entries are DISCLOSED (by seq, content withheld) and the embedded ack is CLAMPED below them =="
(
home="$(fresh_home q12)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
mkdir -p "$home/default"
printf '2' >"$home/default/observed_seq"
f="$TMP_ROOT/q12.jsonl"
{
printf '{"observed_seq":1,"class":"actionable","locators":{"sha":"%s","file":"src/a.ts"},"emit_ts":1}\n' "$SHA40"
printf '%s\n' '{"observed_seq":2,"class":"actionable","locators":{"kind":"board_file","id":"ADDR-Q12","observed_hash":"qq12"},"emit_ts":1}'
} >"$f"
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q12: render must exit 0, got rc=$rc"
# DISCLOSURE: a held entry must be VISIBLE in the digest it was held from —
# a silent hold is how five successive digests each stepped past seq 68...
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
printf '%s' "$out" | grep -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
# ...but WITHOUT re-injecting the refused content: disclosure is by seq only;
# the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands.
printf '%s' "$out" | grep -q 'ADDR-Q12' && fail_msg "Q12: the quarantined entry's content/locators must stay EXCLUDED from the digest"
# CLAMP: the embedded ack stops BELOW the quarantined seq, and says so loudly.
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
printf '%s' "$out" | grep -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
printf '%s' "$out" | grep -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
# A foreign-data render must NOT rewrite the lane's quarantine truth.
[ -e "$home/default/quarantined.set" ] && fail_msg "Q12: a --from-file render must NOT write the store's quarantined.set (lane truth is store-mode only)"
true
) && ok
echo "== Q13 (#946): nothing quarantined -> ack UNCLAMPED at the observed cursor; no disclosure section, no clamp note =="
(
home="$(fresh_home q13)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
mkdir -p "$home/default"
printf '1' >"$home/default/observed_seq"
f="$TMP_ROOT/q13.jsonl"
printf '{"observed_seq":1,"class":"actionable","locators":{"sha":"%s","file":"src/a.ts"},"emit_ts":1}\n' "$SHA40" >"$f"
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q13: render must exit 0, got rc=$rc"
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q13: with nothing quarantined the ack must embed the observed cursor (1) unchanged"
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
printf '%s' "$out" | grep -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
true
) && ok
echo "== Q14 (#946): store-mode render SYNCS quarantine truth into the store — the clamp is enforced END-TO-END at consume =="
(
home="$(fresh_home q14)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
STORE="$SCRIPT_DIR/store.sh"
"$STORE" enqueue --class actionable --locators "{\"sha\":\"$SHA40\",\"file\":\"src/a.ts\"}" >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"board_file","id":"ADDR-Q14","observed_hash":"qq14"}' >/dev/null
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q14: render must exit 0, got rc=$rc"
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry"
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
qf="$home/default/quarantined.set"
[ "$(cat "$qf" 2>/dev/null)" = "2" ] || fail_msg "Q14: a store-mode render must sync quarantined.set to exactly {2}, got [$(cat "$qf" 2>/dev/null)]"
# END-TO-END: even a hand-typed upto past the held seq is refused at the
# store — the copy-run defect (#946) cannot re-land via a different path.
if "$STORE" consume --upto 2 >/dev/null 2>&1; then
fail_msg "Q14: store consume --upto 2 must be REFUSED after the render synced the quarantine"
fi
"$STORE" consume --upto 1 >/dev/null 2>&1 || fail_msg "Q14: consume --upto 1 (the clamped value) must succeed"
) && ok
echo "== Q15 (#946): gate-fix RECOVERY — a stale quarantined.set is REPLACED by a clean store-mode render; the clamp self-heals =="
(
home="$(fresh_home q15)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
STORE="$SCRIPT_DIR/store.sh"
"$STORE" enqueue --class actionable --locators "{\"sha\":\"$SHA40\",\"file\":\"src/a.ts\"}" >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"board_file","id":"OK-NOW","observed_hash":"h","path":"BOARD.md"}' >/dev/null
# A stale set from a broken-gate era: both seqs wrongly quarantined.
printf '1\n2\n' >"$home/default/quarantined.set"
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q15: render must exit 0, got rc=$rc"
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section"
printf '%s' "$out" | grep -Eq 'consumed --upto 2$' || fail_msg "Q15: the ack must embed the full observed cursor (2) once the gate is fixed"
[ -s "$home/default/quarantined.set" ] && fail_msg "Q15: the clean render must REPLACE (clear) the stale quarantined.set — a cumulative-forever set would block acks on entries that now render, got [$(cat "$home/default/quarantined.set")]"
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "Q15: the ordinary consume must succeed after the clamp self-heals"
) && ok
echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconciled exemption must remain load-bearing at the gate (#944 F1) =="
(
self="$SCRIPT_DIR/test-wake-digest-quarantine.sh"
# Token concatenated so THIS guard's own source lines never contain the
# literal fixture id and cannot self-match.
enum_id='ENUM''-B'
fixture_line="$(grep -F "\"id\":\"$enum_id\"" "$self" | grep -F '"observed_seq":5' | head -n1)"
[ -n "$fixture_line" ] || fail_msg "Q16: could not locate Q2's $enum_id fixture line (renamed/renumbered? update this guard)"
fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
# Positive controls FIRST (blind-instrument rule): the extraction must yield
# the real fixture, and the predicate must be able to detect a hard locator.
printf '%s' "$fixture_json" | jq -e . >/dev/null 2>&1 || fail_msg "Q16: extracted fixture is not valid JSON [$fixture_json]"
printf '%s' "$fixture_json" | jq -e '.locators.reconciled == true' >/dev/null 2>&1 || fail_msg "Q16: fixture must carry reconciled:true (wrong line extracted?) [$fixture_json]"
hard_arms='.locators | ((.repo // "") != "" and (((.issue // "") | tostring) != "")) or (((.sha // "") | tostring) | test("^[0-9a-f]{40}$")) or ((.file // "") != "") or ((.path // "") != "")'
printf '%s' '{"locators":{"path":"BOARD.md"}}' | jq -e "$hard_arms" >/dev/null 2>&1 || fail_msg "Q16: positive control failed — the inline hard-locator predicate did not detect a path arm (instrument broken; re-sync it with digest.sh _has_hard_locator)"
# THE GUARD: the fixture must remain ADDRESS-FREE. If it ever gains a hard
# locator, Q2 passes the gate for the wrong reason and the reconciled
# exemption stops being exercised (#944 F1: an exemption nobody exercises is
# as unproven as a gate nobody has seen refuse).
if printf '%s' "$fixture_json" | jq -e "$hard_arms" >/dev/null 2>&1; then
fail_msg "Q16: Q2's $enum_id fixture has grown a hard-locator arm — restore an address-free fixture so the reconciled exemption stays load-bearing [$fixture_json]"
fi
true
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
@@ -19,15 +19,6 @@
# T10 concurrency: two concurrent enqueues get DISTINCT seqs (lock) (#908)
# T12 consume records the last-consumed observed_hash per (kind,id) into the
# store-owned record (additive; monotonic last-seq wins; lazily created) (#932)
# T13 #946 quarantine CLAMP: ordinary consume (store + ack wrapper) REFUSES to
# advance past a quarantined seq; the refusal names the seq + the force flag
# T14 #946 forced step-over: --force-past-quarantine advances LOUDLY, prunes the
# set, and NEVER fabricates a consumed-hash row for the quarantined entry
# T15 #946 quarantine-sync: full REPLACE semantics (sorted/deduped; empty input
# CLEARS — the clamp self-heals once the gate is fixed; invalid input refused)
# T16 #946 quarantine-audit: consumed-hashes rows provably false against the
# dead-letter ledger are reported (exit 1) and removed only under --repair;
# healed rows and the ledger itself are untouched
#
# Isolated: every test runs against a fresh WAKE_STATE_HOME temp dir.
set -uo pipefail
@@ -530,156 +521,6 @@ echo "== T12: #932 — consume records the last-consumed observed_hash per (kind
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3"
) && ok
echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined seq (store + ack paths) =="
(
WAKE_STATE_HOME="$(fresh_state t13)"
export WAKE_STATE_HOME
unset WAKE_AGENT
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"a","observed_hash":"HA"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"b","observed_hash":"HB"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"c","observed_hash":"HC"}' >/dev/null
printf '2\n' | "$STORE" quarantine-sync || fail_msg "T13: quarantine-sync must accept a valid seq list"
# A quarantined seq was dead-lettered at render and NEVER delivered in any
# digest; the ordinary path must REFUSE to record it consumed (#946: a force
# flag the ordinary path can bypass is decoration).
if "$STORE" consume --upto 3 >/dev/null 2>&1; then
fail_msg "T13: ordinary consume --upto 3 must be REFUSED while seq 2 is quarantined"
fi
err="$("$STORE" consume --upto 3 2>&1 >/dev/null || true)"
echo "$err" | grep -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]"
echo "$err" | grep -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
cur="$("$STORE" cursors)"
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T13: a refused consume must NOT advance the cursor [$cur]"
# BELOW the quarantined seq the ordinary path is unaffected.
"$STORE" consume --upto 1 >/dev/null 2>&1 || fail_msg "T13: consume --upto 1 (below the quarantined seq) must succeed"
# The ack wrapper propagates the refusal — no ordinary-path bypass exists.
if "$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1; then
fail_msg "T13: ack.sh consumed --upto 3 must be REFUSED while seq 2 is quarantined (ordinary-path bypass)"
fi
cur="$("$STORE" cursors)"
echo "$cur" | grep -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]"
) && ok
echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabricates a consumed-hash row for the quarantined entry =="
(
WAKE_STATE_HOME="$(fresh_state t14)"
export WAKE_STATE_HOME
unset WAKE_AGENT
rec="$WAKE_STATE_HOME/default/consumed-hashes.jsonl"
qf="$WAKE_STATE_HOME/default/quarantined.set"
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"a","observed_hash":"HA"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"b","observed_hash":"HB"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"c","observed_hash":"HC"}' >/dev/null
printf '2\n' | "$STORE" quarantine-sync || fail_msg "T14: quarantine-sync failed"
errf="$TMP_ROOT/t14.err"
out="$("$STORE" consume --upto 3 --force-past-quarantine 2>"$errf")"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "T14: forced consume must succeed (rc=$rc) [$(cat "$errf")]"
[ "$out" = "3" ] || fail_msg "T14: forced consume must print the new cursor 3, got '$out'"
grep -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
grep -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
# NO FALSE WITNESS: the quarantined entry (repo/b) was NEVER delivered, so no
# consumed-hash row may exist for it — even on the forced path (the reconciler
# re-enumerating it once is safe-but-noisy; a false witness silences it
# forever). Its delivered siblings' rows must exist.
jq_any "$rec" '.kind=="repo" and .id=="a" and .observed_hash=="HA"' || fail_msg "T14: the delivered sibling repo/a must have its consumed-hash row"
jq_any "$rec" '.kind=="repo" and .id=="c" and .observed_hash=="HC"' || fail_msg "T14: the delivered sibling repo/c must have its consumed-hash row"
jq_any "$rec" '.kind=="repo" and .id=="b"' && fail_msg "T14: the quarantined entry repo/b must have NO consumed-hash row (a row would witness a delivery that never happened)"
# The stepped-over seq is PRUNED from the set (it is consumed now; a stale
# entry would re-refuse forever).
grep -qxF '2' "$qf" 2>/dev/null && fail_msg "T14: seq 2 must be PRUNED from quarantined.set after the forced step-over"
# The ack wrapper's force flag passes through, stays LOUD on stderr, and
# still reports a CLEAN cursor line on stdout.
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"d","observed_hash":"HD"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"e","observed_hash":"HE"}' >/dev/null
printf '5\n' | "$STORE" quarantine-sync || fail_msg "T14: quarantine-sync (2nd) failed"
errf2="$TMP_ROOT/t14b.err"
out2="$("$ACK" consumed --upto 5 --no-sync --force-past-quarantine 2>"$errf2")"
rc2=$?
[ "$rc2" -eq 0 ] || fail_msg "T14: forced ack must succeed (rc=$rc2) [$(cat "$errf2")]"
echo "$out2" | grep -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'"
grep -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
jq_any "$rec" '.kind=="repo" and .id=="e"' && fail_msg "T14: the quarantined repo/e must have NO consumed-hash row via the forced ack path either"
true
) && ok
echo "== T15: #946 — quarantine-sync is a full REPLACE (sorted, deduped; empty input CLEARS; invalid input REFUSED) =="
(
WAKE_STATE_HOME="$(fresh_state t15)"
export WAKE_STATE_HOME
unset WAKE_AGENT
qf="$WAKE_STATE_HOME/default/quarantined.set"
printf '3\n1\n3\n' | "$STORE" quarantine-sync || fail_msg "T15: sync of a valid list must succeed"
[ "$(cat "$qf" 2>/dev/null)" = "$(printf '1\n3')" ] || fail_msg "T15: set must be sorted+deduped {1,3}, got [$(cat "$qf" 2>/dev/null)]"
printf '2\n' | "$STORE" quarantine-sync || fail_msg "T15: re-sync must succeed"
[ "$(cat "$qf" 2>/dev/null)" = "2" ] || fail_msg "T15: sync must REPLACE, not merge — expected {2}, got [$(cat "$qf" 2>/dev/null)]"
# Empty input CLEARS the set: the set is re-DERIVED per authoritative render,
# never accumulated, so a fixed locator gate self-heals the clamp.
: | "$STORE" quarantine-sync || fail_msg "T15: empty sync (clear) must succeed"
[ ! -s "$qf" ] || fail_msg "T15: empty sync must CLEAR the set, got [$(cat "$qf")]"
# Invalid input is refused loudly and must not corrupt the set.
printf '1\n' | "$STORE" quarantine-sync || fail_msg "T15: re-seed failed"
if printf 'abc\n' | "$STORE" quarantine-sync >/dev/null 2>&1; then
fail_msg "T15: a non-integer line must be REFUSED"
fi
[ "$(cat "$qf" 2>/dev/null)" = "1" ] || fail_msg "T15: a refused sync must leave the set untouched, got [$(cat "$qf" 2>/dev/null)]"
# End-to-end: a cleared set stops clamping (the #944 recovery case).
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"x","observed_hash":"H1"}' >/dev/null
if "$STORE" consume --upto 1 >/dev/null 2>&1; then
fail_msg "T15: consume --upto 1 must be refused while seq 1 is quarantined"
fi
: | "$STORE" quarantine-sync || fail_msg "T15: clear failed"
"$STORE" consume --upto 1 >/dev/null 2>&1 || fail_msg "T15: after the set is cleared (gate fixed), the ordinary consume must succeed — the clamp must self-heal"
) && ok
echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-letter entry on (kind,id,seq,hash) at/below consumed_seq is PROVABLY FALSE; --repair removes ONLY those rows =="
(
WAKE_STATE_HOME="$(fresh_state t16)"
export WAKE_STATE_HOME
unset WAKE_AGENT
STATE_DIR="$WAKE_STATE_HOME/default"
rec="$STATE_DIR/consumed-hashes.jsonl"
dl="$STATE_DIR/dead-letter.jsonl"
# Rebuild the historical false-witness state via the REAL flow the defect
# used: X@1 was quarantined (dead-lettered) yet consumed under pre-#946 code;
# Y@2 is clean; Z re-emitted (dead-lettered at seq 3, healed by seq 4 winning
# the per-key max_by merge — Finding A's live-canary shape).
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"X","observed_hash":"HX"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"Y","observed_hash":"HY"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"Z","observed_hash":"HZ-OLD"}' >/dev/null
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"Z","observed_hash":"HZ-NEW"}' >/dev/null
{
printf '%s\n' '{"observed_seq":1,"locators":{"kind":"repo","id":"X","observed_hash":"HX"},"class":"actionable","emit_ts":1,"hmac":""}'
printf '%s\n' '{"observed_seq":3,"locators":{"kind":"repo","id":"Z","observed_hash":"HZ-OLD"},"class":"actionable","emit_ts":1,"hmac":""}'
} >"$dl"
# Pre-#946-shaped consume: NO quarantined.set exists, so this consume writes
# the false witness for X@1 exactly as the live defect did.
"$STORE" consume --upto 4 >/dev/null 2>&1 || fail_msg "T16: baseline consume failed"
jq_any "$rec" '.id=="X" and .observed_seq==1' || fail_msg "T16: fixture broken — the false X@1 row was not written"
# REPORT: exactly the X row is provably false; non-zero exit signals findings.
rep="$TMP_ROOT/t16.rep"
if "$STORE" quarantine-audit >"$rep" 2>&1; then
fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist"
fi
grep -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
grep -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
grep -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
grep -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
# Report mode modifies nothing.
jq_any "$rec" '.id=="X"' || fail_msg "T16: report mode must not modify the record"
# REPAIR: exactly the false row is removed; the dead-letter LEDGER is history
# and must never be modified.
"$STORE" quarantine-audit --repair >"$TMP_ROOT/t16.fix" 2>&1 || fail_msg "T16: --repair must succeed [$(cat "$TMP_ROOT/t16.fix")]"
jq_any "$rec" '.id=="X"' && fail_msg "T16: --repair must REMOVE the provably-false X row"
jq_any "$rec" '.id=="Y" and .observed_hash=="HY"' || fail_msg "T16: --repair must keep the clean Y row"
jq_any "$rec" '.id=="Z" and .observed_hash=="HZ-NEW" and .observed_seq==4' || fail_msg "T16: --repair must keep the healed Z@4 row"
[ "$(grep -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair"
# Clean re-audit: OK, exit 0.
"$STORE" quarantine-audit >"$TMP_ROOT/t16.ok" 2>&1 || fail_msg "T16: a clean audit must exit 0 [$(cat "$TMP_ROOT/t16.ok")]"
grep -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake store/ack harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2