Skip to content

Wish write path stops answering what the read path refuses, and the canary gate stops ratcheting itself shut - #710

Merged
MikeyPetrillo merged 7 commits into
mainfrom
claude/sweet-brown-i99jl3
Aug 7, 2026
Merged

Wish write path stops answering what the read path refuses, and the canary gate stops ratcheting itself shut#710
MikeyPetrillo merged 7 commits into
mainfrom
claude/sweet-brown-i99jl3

Conversation

@MikeyPetrillo

Copy link
Copy Markdown
Owner

Two unrelated fixes, both instances of a surface disagreeing with itself.

The wish write response leaked the count the read deliberately withholds. Submitting a wish returned {recorded:true, cluster:{count:N}}, while public GET /api/wishes is a beacon only and the itemized board sits behind the operator token. Now {recorded:true}. The regression guard for the original lockdown turned out to be dead code: it sat below process.exit(), called a __resetWishes that does not exist, and used a positional recordWish signature that never existed. Resurrected, rewritten against the real API, and extended to cover the write response. Suite goes 44 to 54 assertions; restoring the count fails 2 of them.

The paid canary has not bought anything on schedule since 2026-08-02. The gate asked GitHub for the last successful run, but a run that skips the buy also concludes success, so each skip refreshed the timestamp the next gate reads and the 20h window never closed. Across the last 40 runs, every real purchase since the gate shipped came from a manual dispatch, which bypasses the gate. Nothing paged, because skipping is not a failure. The only surface that noticed was /status, which reported settlement stale and showed "Partially measured" on the public page.

The gate now reads the settlement observation from /api/status, which is written only by a canary that actually ran and is the same fact the public page reports, so the gate and the page cannot disagree. It requires fresh and operational, since a recent failed purchase is not proof that buying works. Unreachable status proceeds with the buy. Verified against live production: the old gate skips, the new gate buys.

MikeyPetrillo and others added 5 commits August 7, 2026 08:05
…ses to

Submitting a wish returned { recorded: true, cluster: { count: N } } - the
number of signals that cluster now holds. That is precisely the field the
public read withholds: GET /api/wishes is a beacon (totals, and how many
clusters qualify, never which or how hot), and the itemized board sits behind
the operator token. Two surfaces, same data, opposite answers.

Concretely it let anyone confirm how many others asked for an exact phrase,
and since the threshold is public, how close that phrase is to being built.
The clustering key is only lowercase plus collapsed whitespace, so this
confirms a phrase you already guessed rather than enumerating the board. That
makes it narrow, not harmless, and it costs nothing to close: the caller asked
us to record a gap, and { recorded: true } says we did.

The regression guard for this was already written and had never run. It sat
below process.exit(), called a __resetWishes that does not exist, and used a
positional recordWish signature that has never existed - it would have thrown
on its first line if it had ever executed. The confidentiality lockdown was
guarded by a comment. Moved above the summary, rewritten against the real API,
and extended to the write response: no cluster object, and no digit anywhere
in the body. The last assertion checks the token-gated board still knows the
real count, so the assertion above it has something to hide - otherwise both
would pass against a board that simply lost the data.

Mutation-tested: restoring the count fails 2 assertions. Suite goes 44 -> 54,
the difference being the block that never ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The daily proof that buying works has not run on schedule since 2026-08-02,
and reported success every time.

The gate asked GitHub for the last SUCCESSFUL RUN of this workflow. A run
whose gate skips the buy also concludes success, so every skip refreshed the
timestamp the next gate reads. Three attempts a day, each one landing inside
the 20h window opened by the previous skip, means the window never closes.
Measured over the last 40 runs: the gate shipped 2026-08-02, and not one
scheduled run has bought since. Every real purchase after it came from a
manual dispatch, which bypasses the gate entirely because the step is
conditioned on github.event_name == 'schedule'. The last of those was
2026-08-05 04:16 UTC.

The commit that introduced this said it would make the workflow "run on the
days it claims to". It stopped it running at all.

Nothing paged, because skipping is not a failure. The only surface that
noticed was /status, which reported the settlement component stale and put
"Partially measured" on the public page - working exactly as designed, since
a day with no observation is no data rather than uptime.

So the gate now asks production when a canary last actually BOUGHT, reading
the settlement observation from /api/status. That observation is written only
by a canary that ran, and it is the same fact the public page reports, so the
gate and the page can no longer disagree - and if this class of bug returns,
the page goes amber again, which is how it was caught.

Two conditions, not one: fresh AND operational. A recent observation of a
FAILED purchase is not proof that buying works, and suppressing the next
attempt on the strength of it would turn three attempts to succeed into one
attempt to fail. Unreachable status, or no observation on record, proceeds
with the buy - a gate that cannot check must not block, the same
fail-toward-running direction the job already documents.

Verified against live production before commit: with the settlement
observation 2.3 days old and state unknown, the old gate skips and the new
gate buys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two ways the new gate could have stopped the monitor it protects.

jq exits 5 on a body that is not JSON - an error page, an interstitial, a
truncated response - and under `set -euo pipefail` that fails the step, which
fails the gate job. Both reads now fall back to "none", so every parse failure
lands on the "no observation, so buy" path. The step cannot fail.

And the canary job's own comment was wrong. It said an errored gate leaves
skip empty and the canary RUNS, but a job-level `if` with no status check
function still carries the implicit success() on `needs`, so a failed gate
would have SKIPPED the canary - a monitor stopping because its guard broke,
which is the exact failure the comment claims to prevent. `!cancelled()`
overrides that implicit check and makes the sentence true. Not always(): a
cancelled run should stay cancelled rather than go spend money.

Simulated all six paths against the real jq filters: empty body, HTML error
page, valid JSON with no settlement component, today's genuinely stale
observation, fresh+operational, and fresh-but-failed. Only fresh+operational
skips; every uncertainty buys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MikeyPetrillo
MikeyPetrillo marked this pull request as ready for review August 7, 2026 12:10
MikeyPetrillo and others added 2 commits August 7, 2026 08:13
CI caught the workflow change, which is the system working. But the assertion
it caught it with was checking the wrong thing, and one beside it was worse.

"an errored or skipped gate leaves the canary RUNNING, never silently
disabled" tested only that the comparison reads `!= 'true'` rather than
`== 'false'`. That is about the polarity of an output, and says nothing about
a gate that FAILED - which is governed by the implicit success() on `needs`,
not by the comparison. It passed for weeks against code where a failed gate
would have skipped the buy: the exact opposite of the sentence it asserts.
Same shape as the wish confidentiality guard in this branch, and the same
shape as a green suite hiding a dead fix - a test that supplies the answer it
is supposed to check.

It now reads the canary job's actual `if` line and requires a status function,
which is the thing that makes the claim true, and reports the line it found so
a failure names the real text.

Two new locks on the regression that started this. The gate must read
/api/status and must NOT read `gh run list`: keying on this workflow's own run
history is what let every skip refresh the window the next gate reads. And
every jq read in the gate must carry a fallback, since jq exits non-zero on a
body that is not JSON and `set -e` would turn that into a failed gate.

Mutation-tested one at a time: dropping !cancelled() fails 1, restoring the
`gh run list` gate fails 1, removing the jq fallbacks fails 1. Each mutation
is killed by its own assertion and no other, so none of the three is carrying
the others.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MikeyPetrillo
MikeyPetrillo temporarily deployed to agent402 / production August 7, 2026 12:28 — with GitHub Actions Inactive
@railway-app
railway-app Bot temporarily deployed to agent402 / production August 7, 2026 12:28 Inactive
@MikeyPetrillo
MikeyPetrillo merged commit 7939cb3 into main Aug 7, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant