Skip to content

feat(budget): sliding-window cost accounting for agent exchanges - #1

Draft
mfethe1 wants to merge 14 commits into
mainfrom
worktree-buzz-budget-accountant
Draft

feat(budget): sliding-window cost accounting for agent exchanges#1
mfethe1 wants to merge 14 commits into
mainfrom
worktree-buzz-budget-accountant

Conversation

@mfethe1

@mfethe1 mfethe1 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Implements the accounting half of the loop-protection policy decided in wayfinder ticket mfethe1/agent-mesh#7.

Draft — this crate has no consumer yet, by design. It stops at a seam; see Deliberately not implemented.

Why

Buzz already has throttles — thread depth capped at 100, tiered rate limits (human 60 / agent 120–600 msg/min), in-flight deadlines, session rotation. It has no termination condition. Two agents that open fresh threads at each other never accumulate thread_metadata.depth, so they run at a rate-limited 120 messages a minute indefinitely, each one a paid LLM call.

What

crates/buzz-budget budgets the thing that actually matters: measured cost_usd from the kind:44200 turn metric (NIP-AM).

  • $5 per (channel, agent-pair) per rolling hour. The pair is unordered — A→B and B→A share one budget, because a runaway is a property of the pair, not a direction.
  • Self-healing. The window slides; there is no reset step.
  • Human-triggered turns are never charged. Cutting off someone who is sitting there watching is the failure this policy exists to avoid.

Four modules: ingest (wire → values, the only part that knows Nostr), origin (attributes a turn to its trigger), Ledger (the sliding window), Supervisor (composes them so a caller cannot get the order wrong).

Deliberately not implemented

Enforcement. record() returns a Verdict; acting on it is the caller's job. Buzz's owner commands are !shutdown (kills the whole harness), !cancel (one turn) and !rotate (session) — none can mute a single peer, so enforcing this needs a new buzz-acp command. Keeping the accounting pure means that decision can land without touching this code.

Also absent: the relay subscription loop, which needs an enforcement host (ticket block#7 OQ7.3).

Known limitations, carried honestly

  • Attribution fails open. An observation gap yields no trigger, which is not charged — so a dropped subscription disables the budget rather than tripping it.
  • The attribution boundary is end-of-turn. The metric carries no turn start, so a message arriving mid-turn is attributed to the turn it did not trigger, and one owner message can free two turns. Errs toward under-charging. The durable fix is a turn-start or trigger field on NIP-AM (Improve message markdown display and formatting block/buzz#7 OQ7.5).
  • A harness reporting no cost_usd is invisible to the budget (Improve message markdown display and formatting block/buzz#7 OQ7.2).

Review

Both axes of /code-review ran against this branch and found two real bugs, since fixed in 72d86f0:

  • Securityturn_end was taken from the agent's own encrypted payload and never checked, so a future-dated metric wiped the pair's window. A runaway could zero its own budget. Now tied to the signed created_at.
  • Correctness — trigger consumption was per channel rather than per agent, so with two agents in one channel the first metric ate the other's trigger. Relay reordering alone disabled the budget.

Verified at runtime, not just tested

cargo run -p buzz-budget --example runaway drives the supervisor with a real tracing subscriber. Two of the policy's claims hold under observation, and one does not:

Claim Observed
D5 — human turns never blocked 40 owner-driven turns @ $2.50: 0 blocked, $0.00 charged
D4 — self-healing, no reset after the window slid, spend reset and the verdict returned to Allow
D4 — runaway leaves a sawtooth in the logs no sawtooth — spend climbs monotonically $5.40 → $18.00 across 22 consecutive WARN lines

The sawtooth is a property of enforcement, not accounting. Until something acts on the verdict, the "diagnosable signature" D4 promised is a continuous alarm — arguably worse than the silent drain it was meant to replace. That is a flaw in the decision's reasoning rather than in this code, recorded as #7 OQ7.6.

Checks

41 tests + 1 doctest passing. cargo clippy clean, cargo fmt --check clean. #![deny(unsafe_code)] and #![warn(missing_docs)] per CONTRIBUTING.md.

tlongwell-block and others added 12 commits August 6, 2026 22:26
…block#5133)

## What

Relay-only carve-out of the ingest half of block#4999: generic EVENT ingest
now accepts kind:30179 (NIP-PMA private managed-agent config). One file,
`crates/buzz-relay/src/handlers/ingest.rs`, 16 insertions / 15
deletions; **two semantic lines**, byte-identical to the ingest hunk of
block#4999 at `6f486e88`:

1. `required_scope_for_kind`: 30179 requires `Scope::UsersWrite` — same
arm as its public sibling 30177 and the other owner-authored NIP-AP
kinds.
2. `is_global_only_kind`: 30179 is owner-global, keyed `(pubkey, kind,
d-tag)`; a stray `h` tag must not channel-scope it.

The rest is import reflow plus replacing the guard test with a positive
one (`private_managed_agent_kind_is_owner_scoped_global_user_data`:
asserts UsersWrite scope, global-only, no h-channel scope).

## Why the guard test can be retired

The removed test
(`private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists`)
pinned a stated precondition: *"must not enter generic EVENT ingest
before privacy and aggregate CAS deploy."* Both halves are resolved:

- **Privacy** — the author-only read gates for 30179 shipped to main
with block#4593: `AUTHOR_ONLY_KINDS` membership, `req.rs` pre-filter + result
gates, `count.rs`, `event.rs` fanout, and the bridge pre-filter
(`bridge.rs:999-1000` returns `restricted: author-only kinds require
authors=[self]` / 403). Only the author can read the event back.
- **Aggregate CAS** — block#4999 settled generation as **advisory**: the `g`
tag is shape-validated, never relay-enforced. Last-write-wins per
coordinate is the contract of record (see the kind:30179 contract blurb
in block#4999), so no CAS mechanism is pending on the relay side.

## Why this is inert to existing relays and clients

- No production desktop code on main authors kind:30179 — the codec
(`private_managed_agent.rs`) has zero non-test callers. This PR accepts
a kind nobody can produce yet.
- Content is opaque NIP-44 ciphertext to the relay; the relay never
decrypts it.
- Reads remain author-only via the already-shipped gates above.
- Storage is the standard parameterized-replaceable path already
exercised by kinds 30175–30178. No schema, config, or migration changes.

## Testing

- Full `buzz-relay` package suite at this commit: 859 passed, 1 failed —
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` (504
vs 200), which **reproduces identically on clean main `769ac70b`** with
this change stashed; pre-existing/environmental, not introduced here.
- New positive ingest test passes.
- Pre-push hooks green (branch-skew, rust-tests, desktop-tauri-checks).

## Relationship to block#4999

block#4999 (relay-primary agent config, desktop half) stays DO-NOT-MERGE
pending live relay receipts + real CI; once this lands and deploys, its
live test simplifies to plain `desktop-standalone` against the real
relay, and block#4999 rebases to drop its now-duplicate ingest hunk
(identical bytes → trivial rebase).

Originating thread:
buzz://message?channel=06f13ed3-0557-4ac2-922c-1545dd00bf97&id=2a43b3b4933a2ea78b77088619251c061355f9b7b6dc29ea0d702193f2344149


## Brownfield FTS note (review findings, operator-ruled non-blocking for
this PR)

Max and Sami independently identified that the FTS privacy skip-set is
regime-dependent: migration 0008 installs the positive allowlist (`kind
IN (0, 9, 40002, 45001, 45003)`) **only on an empty events table**; an
already-populated database keeps the 0001/0005 negative skip-list
(wrapped by 0014 to add 30350), which omits 30179 — so on such an
installation this PR admits 30179 rows whose NIP-44 ciphertext gets
indexed by `to_tsvector`. Sami measured both regimes against real
Postgres (brownfield: 30179 INDEXED; fresh: NULL) and demonstrated the
existing drift test only exercises the fresh regime.
`schema/schema.sql:222`'s canonical literal is also the negative list
and omits 30179. Migration dates put any relay deployed with data before
0008 landed (2026-07-13) in the brownfield class.

**Scope of exposure (Sami's trace):** not a content leak —
`event_visible_to_reader` / `is_author_only_event` gates hold on both
search surfaces (`req.rs:725`, `bridge.rs:1770`), so foreign readers
receive nothing. Lost is the storage-level NULL-tsv backstop plus FTS
page budget burned on post-filtered hits.

**Operator ruling (Tyler, events `1472e5b6`, `cbd368ed`):** ship this PR
without an exclusion migration. Safety argument that makes this sound
rather than merely accepted: main has **zero non-test 30179 writers**
until block#4999's desktop half deploys — no 30179 rows can exist, so nothing
can be indexed in any regime while this PR is the only half live.



**Additional review characterizations (Sami, non-blocking, on the
record):**
- *Behavioral delta enumerated:* routing triple
(`required_scope_for_kind` / `is_global_only_kind` /
`requires_h_channel_scope`) compared for all 65,536 kinds at base
`769ac70b` vs head `77eeba6e` — exactly one row differs (30179). No
other kind or client changes behavior.
- *"SQL visibility before LIMIT" (NIP-PMA step 2):* no
`AUTHOR_ONLY_KINDS` pushdown clause exists in `buzz-db` (only
`SHARED_GATED_KINDS` has one). Author-only kinds are protected by the
pre-filter (`author_only_filters_authorized`) plus post-filter omission;
mixed-kind filters can burn candidate-page budget on discarded rows.
Pre-existing and identical for 30300/30350 — not introduced here; noted
so the NIP's step-2 checkbox is not read as fully ticked.
- *Envelope validation gap:* 30179 is the only parameterized-replaceable
kind at ingest with no per-kind envelope validator (codec grammar checks
run in the desktop writer, not the relay). Generic limits only (256 KiB,
±15 min, pubkey==identity, d-tag bound). Self-inflicted footgun bounded
to the author's own coordinate — candidate companion to the exclusion
migration in the block#4999 rebase, deliberately not added here.

**Bound follow-up (required before/with the block#4999 desktop half):** a
0014-shape additive migration (`pg_get_expr` capture + `CASE WHEN kind =
30179 THEN NULL ELSE (<existing>) END` wrap), add 30179 to the
`schema/schema.sql:221` literal, and a brownfield-regime variant of the
FTS drift test, per Sami's finding. Deploy-time spot check if ever
wanted: `SELECT pg_get_expr(d.adbin, d.adrelid) FROM pg_attrdef d JOIN
pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE
d.adrelid = 'events'::regclass AND a.attname = 'search_tsv';`

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
…lock#5136)

## Problem

The harness posts each trial's task via `buzz messages send`, relying on
`@<orchestrator-id>` name resolution. Task text is untrusted payload:
when it contains @-tokens of its own, the CLI's mention resolver tries
to resolve them as channel members, fails, and refuses to send — killing
the trial with `RuntimeLaunchError` before the agent ever saw the task.

Live occurrence: TB 2.1's `large-scale-text-editing` task embeds Vim
macros (`:%normal! @a`). In the tb21-solo-1 run the trial died at
launch:

```
RuntimeLaunchError: buzz messages send ... exited 1:
{"error":"user_error","message":"mention '@A' does not match a current channel member; retry with --mention <pubkey>"}
```

Any TB task whose statement contains @-syntax is silently zeroed this
way.

## Fix

Pass the orchestrator's pubkey as an explicit `--mention` when posting
the task. The CLI demotes unresolved @-tokens in the text to
presentation-only when any explicit identity is supplied, so delivery
still targets exactly the orchestrator and every @-token in the task
statement becomes inert. The harness already holds the orchestrator's
`AgentCredential` (it writes that pubkey into the worker roster tables),
so no persistence is needed — fresh key per trial, fresh `--mention` per
trial.

Verified both halves against a live relay: a fenced `@a` without
`--mention` still hard-fails (the resolver is not markdown-aware); the
same content with an explicit `--mention` sends clean with
`mention_pubkeys` containing only the target.

## Testing

- `benchmarks/harbor-buzz-orchestra`: full pytest suite — 35 passed (34
baseline + new `test_send_mentions_by_pubkey_so_task_text_stays_inert`),
ruff clean. Run against `origin/main` 769ac70 with exactly this patch
applied.
- `testbed`: full pytest suite — 23 passed, 1 skipped; ruff clean.

## Acceptance

A task statement containing arbitrary @-tokens (Vim registers, emails,
decorators) launches and delivers to the orchestrator instead of dying
in `_send`.

Originating Buzz thread:

`buzz://message?channel=c3252dd2-0142-4e01-88c7-a2183c3960a5&id=74a65a0990fd2197882b66b5ea2707169d4a3dbd2020d1610c45150fb99f140b`

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
…uth extraction fails (block#4824)

emit structured JSON diagnostics when NIP-OA owner-auth extraction fails
during `buzz agents archive`/`unarchive`

## Problem

When owner-auth extraction returned `None`, the CLI silently sent a bare
request. The relay replied with `400: missing auth tag` and the caller
had no way to know why extraction failed.

## Solution

Extract `resolve_auth_from_profile` — a sync function that owns all
three warning branches and the success path. `resolve_auth` reduces to:
self-check → fetch kind:0 → delegate.

- **Four distinct diagnostics**: no kind:0 profile / no tags array /
`classify_owner_auth_tag` failure (typed `AuthFailure` enum:
`NoAuthTag`, `AmbiguousAuthTag`, `WrongArity`, `NonStringElement`,
`InvalidOwnerHex`, `InvalidSigHex`, `OwnerMismatch`)
- **JSON format**: each fallback emits exactly one `{"warning":"..."}`
line to stderr, matching the CLI's documented structured-stderr contract
and the precedent in `channels.rs:597`
- **Relay-supplied values** (target pubkey, actual owner pubkey) pass
through `serde_json` serialization — no unescaped text
- **Admin bare path preserved**: request is always sent after the
warning; bare non-self requests are legitimate for relay admins
- **Self path unchanged**: silent, no relay query

## Boundary tests

Tests call `resolve_auth_from_profile` directly with `&mut Vec<u8>`.
Each of the three production `writeln!` calls is covered: deleting any
one fails at least one test. Success path asserts zero bytes written.

## Changes

`crates/buzz-cli/src/commands/agents.rs` only:
- `AuthFailure` enum with `message()` formatter
- `classify_owner_auth_tag` returning `Result<[String;4], AuthFailure>`
- `extract_owner_auth_tag` reduced to `#[cfg(test)]` `.ok()` wrapper
- `resolve_auth_from_profile` sync helper (testable without
`BuzzClient`)
- `resolve_auth` reduced to self-check + fetch + delegate
- 9 new boundary tests replacing the prior test-local helper

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary
- move **Run on** into Advanced, directly after **Who can send
instructions**
- reuse the modal’s shared dropdown styling
- give the Welcome guidance and composer matching glass treatment while
preserving the corrected exit layering

## Validation
- `pnpm -C desktop typecheck`
- focused Playwright: Run on configuration (3 passed)
- focused Playwright: Welcome onboarding flow (1 passed)
- desktop unit suite (4,290 passed)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
Implements the accounting half of the loop-protection policy decided in
wayfinder ticket block#7 (mfethe1/agent-mesh#7).

Buzz already has throttles — thread depth capped at 100, tiered rate
limits, in-flight deadlines, session rotation — but nothing that says an
exchange is finished. Two agents that open fresh threads at each other
never accumulate depth, so they run at a rate-limited 120 messages a
minute indefinitely, each one a paid LLM call.

This crate budgets the thing that actually matters: measured cost_usd
from the kind 44200 turn metric, $5 per (channel, agent-pair) per
rolling hour. The window slides, so it self-heals with no human reset.
Human-triggered turns are never charged — cutting off someone who is
sitting there watching is the failure the policy exists to avoid.

Deliberately does not enforce. record() returns a Verdict and the caller
decides. Buzz's owner commands (!shutdown, !cancel, !rotate) cannot mute
a single peer, so the enforcement mechanism is still open; keeping the
accounting pure lets that decision land without touching this code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
Decision D5 of wayfinder block#7 says only agent-triggered turns consume
budget. Implementing it surfaced a gap: the kind 44200 turn metric has
no field naming the author that caused the turn. Its payload is harness,
model, channel_id, session_id, turn_id, turn_seq, timestamp, turn,
cumulative, delta_reliable and stop_reason — nothing about the trigger.

TriggerLog recovers it without a wire-format change, using a property
buzz-acp already guarantees: turns are serialised per channel, and all
pending events for a channel drain into one batch. So the messages seen
in a channel since its last turn are that turn's trigger.

A batch mixing human and agent messages counts as human. D5 exists to
protect the case where a person is present and watching; charging that
turn risks muting an agent mid-conversation with its owner. Erring the
other way costs a runaway one extra turn before it trips.

Attribution is best-effort and fails open: an observation gap yields
None, which is not charged, so a dropped subscription disables the
budget rather than tripping it. The durable fix is a trigger field on
NIP-AM, which the spec's forward-compatibility rule already permits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
Ledger charges a pair; TriggerLog attributes a turn. Held separately, a
caller has to remember to attribute before charging and to prune both on
the same schedule. Supervisor owns that sequencing so it cannot be got
wrong, and gives the crate one entry point instead of two loose halves.

Adds the integration coverage the pieces lacked. The end-to-end runaway
now has a test: two agents alternating at $0.30 a turn trip the $5
budget on the seventeenth charged turn. So does the failure this policy
must never cause — two hundred consecutive owner-driven turns at $1.50
each, none blocked, nothing charged.

Also covers a human joining a runaway mid-flight, which makes that turn
free because the mixed batch attributes to the human; separate pairs in
one channel not pooling budget; and an unobserved turn failing open.

Writing the runaway test caught a real arithmetic error in its own
expectation, not in the code: $5 at $0.30 a turn first exceeds budget on
the seventeenth charge, not the eighteenth.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
The crate could account and attribute, but nothing connected it to real
data. charge_from_metric decrypts a kind:44200 event with the owner's
keys and extracts channel, agent, cost and end-of-turn timestamp.

Kept in its own module because it is the only part of the crate that
knows about Nostr. Ledger and TriggerLog stay testable on plain values
with no keys and no events, which is why they have the coverage they do.

Tests build real signed events with buzz-core's own encrypt helper and
round-trip them, so this is checked against Buzz's actual wire format
rather than a hand-rolled fixture. A stranger's key fails closed, an
unrelated kind is rejected before any decrypt attempt, and missing or
malformed channel ids and timestamps are errors rather than silent
defaults.

Notable while writing it: decrypt_agent_turn_metric already rejects
negative and non-finite costUsd per NIP-AM, so the ledger's own guard
against a refund is defence in depth rather than the only barrier.

delta_reliable is surfaced on TurnCharge rather than swallowed — block#7
OQ7.1 asks how often it is false, and that cannot be answered if the
ingest layer discards it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
Two real bugs, one security and one correctness, plus the documented
standards this crate was skipping.

Security: charge_from_metric took turn_end from the agent's own
encrypted payload and never checked it. AgentTurnMetricPayload::validate
covers only the numerics, so a metric dated in the future moved the
ledger's eviction cutoff forward and wiped the pair's whole window — the
budgeted agent could zero its own budget at will. The payload timestamp
is now tied to the signed created_at within a 300s tolerance.

Correctness: TriggerLog consumed the batch per channel rather than per
agent, so with two agents in one channel whichever metric was processed
first ate the other's trigger and the second turn went uncharged. Relay
reordering alone disabled the budget. Consumption is now tracked per
(channel, agent) with independent cursors.

The spec axis found a third defect that per-agent cursors do NOT fix:
the metric's timestamp is end-of-turn and the payload carries no start,
so a message arriving mid-turn is attributed to the turn it did not
trigger, and one owner message can free two turns. It errs toward
under-charging like every other approximation here, and the durable fix
is a turn-start or trigger field on NIP-AM. Documented rather than
papered over — it is a second argument for block#7 OQ7.5.

Both reviewers independently flagged Verdict::Allow{spent_usd: 0.0} on
human and unattributed turns as a lie: the pair may hold $4.90, and a
caller logging spend would see a sawtooth already collapsed to zero.
Replaced with an explicit Unbudgeted variant.

Standards: adds deny(unsafe_code) and warn(missing_docs) per
CONTRIBUTING.md, documents the public API those lints then surfaced, and
registers the crate in the AGENTS.md crate map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
The spec-review agent cited agent_turn_metric.rs:107 for the end-of-turn
timestamp and I carried that into origin.rs without opening the file.
Line 107 is turn_seq documentation; the RFC 3339 end-of-turn line is 111.

Every other cross-crate citation in the crate was re-checked by opening
the cited line: buzz-acp/src/lib.rs:192 (is_owner_or_sibling),
buzz-acp/src/queue.rs:1-7 (per-channel serialisation),
agent_turn_metric.rs:86 (forward-compatibility rule) and :1-5 (NIP-44
addressing). All four hold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
Two gaps the spec review found, both now closed.

D4 justifies a self-healing window by the sawtooth it leaves in the
logs — "a diagnosable signature rather than a silent drain" — but the
crate had no tracing dependency and emitted nothing, so the signature
the decision rests on did not exist. Exhaustion now logs at warn with
the pair, spend and budget; ordinary charges log at debug.

delta_reliable was populated by the ingest layer and read by nobody, so
D2 charged unreliable deltas at face value with no seam for the fallback
OQ7.1 asks about. on_turn_charge takes a wire-decoded TurnCharge, warns
when the publisher lost its cumulative baseline, and still charges —
because OQ7.1 is genuinely undecided and its suggested fallback needs
cumulative, which TurnCharge does not yet carry. Explicit and logged
beats silent, and it lets the question be answered from real data.

Both behaviours are tested rather than left accidental.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 619c8990-e8af-4f2a-a854-6cc4b38dbd4a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Michael Feth and others added 2 commits August 8, 2026 01:56
…sing

The verification gate is right that tests are not runtime evidence. This
example launches the supervisor with a real tracing subscriber and shows
what an operator would actually see.

Two of D4's claims hold under observation. Forty consecutive
owner-driven turns at $2.50 each: zero blocked, zero charged. After the
window slides, spend resets and the verdict returns to Allow with no
reset step.

The third does not. D4 justifies the design by predicting "a runaway
stops within minutes and restarts only to stop again, producing a
sawtooth in the logs — a diagnosable signature rather than a silent
drain." Running it produces no sawtooth: spend climbs monotonically from
$5.40 to $18.00 across 22 consecutive WARN lines, because nothing acts
on the verdict.

The sawtooth is a property of enforcement, not of accounting. Until an
enforcer exists the signature is a continuous alarm, which is arguably
worse for diagnosis than the silent drain it was meant to replace. That
is a flaw in the decision's reasoning rather than in this code, and it
is recorded on the ticket.

spent_usd is logged raw rather than rounded. The float noise is real
data and these are structured events meant for machine aggregation;
rounding at the emit site would lose precision for no gain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
The spec-review agent flagged it as unrequested; spot-checking that
claim found exactly one caller, and it was the test asserting on it.
A public accessor whose only consumer is its own test is not API, it is
speculative generality.

The test it existed for still matters — both argument orders must reach
one budget — so it now asserts that directly by hashing both keys into a
set, which is closer to how PairKey is actually used.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
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.

4 participants