Skip to content

fix(cat): the outcome gate reported a cause it had never tested - #829

Merged
catomean merged 1 commit into
mainfrom
fix/cat-gate-cause
Aug 29, 2026
Merged

fix(cat): the outcome gate reported a cause it had never tested#829
catomean merged 1 commit into
mainfrom
fix/cat-gate-cause

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

The nightly gate paged with

GATE FAILED: assistant messages exist in the window but 0 Cat actions were logged — the cat_action_log write path looks dead (check RLS policies and the executor).

The write path is not dead. cat_action_log held ten rows for that window, the newest from 2026-08-25. The gate had never looked at it.

What it actually measured

proposed counts only rows that are both status=completed and a create_* action. In that window there were none — the single create_* was create_cause, and it was denied. Three different worlds produce proposed === 0:

Situation Correct response
1 nothing is written at all the audit trail is dead — page
2 writes happen, no completed creates nothing creatable was asked for, or every attempt was denied — not an outage
3 no assistant activity at all nothing to grade

The old predicate could not tell 1 from 2, so it asserted the diagnosis for 1 whenever it saw either — a cause it had never tested.

This matters beyond the noise: a tripwire that cries every night is one nobody believes on the night it is right, which is precisely the failure this gate exists to catch.

The fix

It now fetches the window's rows with no status or action filter — "is anything written at all" is the only question that distinguishes the dead-write-path bug — and fails only on that shape. Otherwise it reports what it found, including denials, which are a real signal that was being swallowed.

Testability was the root cause

The predicate stayed wrong because nothing could reach it: the decision lived inline in main(), behind network calls, in a script that ran on import. It is now a pure gateVerdict() and main() runs only on direct execution.

Verification

  • 9 tests (7 new), run in a child process against the real module, matching eval-cat-retry's pattern.
  • Mutation-proven: restoring the old predicate turns three of them red. The artifact was then verified byte-identical to its pre-mutation state.
  • Live against production, exits 0:

    gate: the write path is alive — 10 action(s) logged in window (denied 4, completed 5, failed 1), just none of them a completed create_*. Not an outage. NOTE: 4 were DENIED — worth a look if that is unexpected.

Worth a separate look

Of the 10 actions in the window, 4 were denied (publish_entity ×2, create_cause, add_wallet) and one send_payment failed. Not an outage, but not obviously intended either — the fixed gate surfaces it instead of hiding it behind a wrong diagnosis.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz

The nightly gate failed with "assistant messages exist in the window but 0 Cat
actions were logged — the cat_action_log write path looks dead (check RLS
policies and the executor)" while cat_action_log held TEN rows for that window,
the newest from 2026-08-25. The write path was alive. The gate had never looked
at it.

`proposed` counts only rows that are BOTH status=completed AND a create_*
action. In the window there were none — the single create_* was `create_cause`,
DENIED. Three different worlds produce proposed === 0:

  1. nothing is written at all           → the audit trail is dead. Page.
  2. writes happen, no completed creates → nothing creatable was asked for, or
                                           every attempt was denied. Not an outage.
  3. no assistant activity at all        → nothing to grade.

The old predicate could not tell 1 from 2, so it asserted the diagnosis for 1
whenever it saw either. It now fetches the window's rows with no status or
action filter and fails only on the shape it was built for: the log EMPTY while
the Cat is talking. Otherwise it reports what it found — including denials,
which are a real signal worth seeing and were being swallowed.

This matters beyond the noise: a tripwire that cries every night is one nobody
believes on the night it is right, which is the exact failure it exists to catch.

The predicate is now a pure function and main() only runs on direct execution,
so the decision can be tested without a database — it stayed wrong because
nothing could reach it. Seven cases added, run in a child process against the
real module (matching eval-cat-retry's pattern). Proven by mutation: restoring
the old predicate turns three of them red, and the artifact was verified
byte-identical afterwards.

Live against production: exits 0 with
  "the write path is alive — 10 action(s) logged in window (denied 4,
   completed 5, failed 1), just none of them a completed create_*."

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz
@catomean
catomean merged commit ed1d961 into main Aug 29, 2026
6 checks passed
@catomean
catomean deleted the fix/cat-gate-cause branch August 29, 2026 05:30
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