Wish write path stops answering what the read path refuses, and the canary gate stops ratcheting itself shut - #710
Merged
Conversation
…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
marked this pull request as ready for review
August 7, 2026 12:10
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
temporarily deployed
to
agent402 / production
August 7, 2026 12:28 — with
GitHub Actions
Inactive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 publicGET /api/wishesis 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 belowprocess.exit(), called a__resetWishesthat does not exist, and used a positionalrecordWishsignature 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.