feat: x402 payment requirement for POST /api/signals#802
Conversation
Lays groundwork for x402-paid signal submissions: - Extend PaymentStageKind union and PaymentStagePayload variants with SignalSubmissionStagePayload. - Add 'pending_payment' to SIGNAL_STATUSES (kept out of REVIEWABLE_*). - Replace inline if/else if branches in reconcileStageRow with a kind→finalize callback registry. Behaviour for brief_access and classified_submission is byte-identical; finalizeSignalSubmission is the new third entry. - Schema migration v30: add signals.payment_txid column and (status, btc_address, created_at) index for cooldown/cap queries on pending rows. - Allowlist signal_submission on the /payment-staging reconcile route. No route or behaviour changes yet — that lands in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a staged signal_submission payment terminates as failed / replaced / not_found, delete the pending_payment signals row + its signal_tags so the agent's cooldown / daily-cap slot is released immediately. brief_access and classified_submission keep a no-op discard since their finalize side effects only fire on `confirmed` and there is nothing to undo. correspondent_stats drift on discard is intentional and reconciled by the existing POST /api/config/recon-correspondents endpoint — discards are rare under a stable relay. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the streak / correspondent_stats / referral side effects of an inserted signal into three free helpers (applyCorrespondentStatsBump, applyStreakBumpForSignal, applyReferralCreditOnFirstSignal) and wire them into both the createSignal route (existing path) and the new finalizeSignalSubmission hook. Stage path: createSignal accepts pending_payment + caller-provided signal_id. When pending_payment=true the row lands at status='pending_payment' and all three commit effects are skipped — totals stay aligned with finalised signals only. Finalize path: finalizeSignalSubmission re-reads the row to confirm it is still pending_payment (idempotency under poll+sweep races), flips status, and runs the three helpers using the signal's own created_at so x402 settlements that cross UTC midnight still credit the correct day. Discard path: deleting the pending_payment row is now an exact reverse of stage — no stats / streak / referral mutations to roll back. Correspondent_stats reads (drift recompute, per-agent recompute, migration backfill) gain `status != 'pending_payment'` so the materialised totals stay consistent with the new bump rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the classifieds.ts payment template:
- 402 missing X-PAYMENT (with payment.required event)
- payment.retry_decision logged on verifyPayment failure (409 / 503 /
402 retryable / non-retryable)
- payment.accepted on verification.valid
- HTTP-fallback (confirmed && !paymentId) writes the signal directly with
status='submitted' and payment_txid, returns 201
- Stage path (paymentId present) inserts the signal at status='pending_payment'
with caller-allocated id, stages the x402 record, then either reconciles
in-band (confirmed → 201) or returns 202 with checkStatusUrl (pending)
- Cooldown / daily-cap checks fire pre-stage so an over-limit agent never
sees a staged-payment orphan
Pass {logger, route} into verifyPayment so HTTP-fallback warnings flow into
structured logging. Drop the soon-to-be-stale grace-period warning;
disclosure-empty warning preserved.
createSignal route handler in news-do.ts gains payment_txid + signal_id +
pending_payment inputs to support the stage / fallback paths cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…derboard Default reads now exclude x402-staged-but-unconfirmed signals so they only become visible after the relay confirms the payment: - buildSignalListWhere defaults to excluding pending_payment unless an explicit status='pending_payment' filter or include_pending=true is set. - querySignalCountRows skips pending_payment unless an agent is scoped (an author looking at their own staged count) or include_pending is set. - queryLeaderboard excludes pending_payment from the seeding btc_address set and the 30-day signal_count / days_active subqueries so a stage that later fails cannot inflate scores. Routes (/api/signals, /api/signals/counts) and do-client thread the new include_pending flag through. status='pending_payment' on the list route is also accepted for authors who want only their staged rows. Front-page query, beat-stats since query, /signals/:id, and create / cap / cooldown queries are intentionally left as-is — they either already filter by status, only need the row by id, or count pending_payment toward the rate-limit slot on purpose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TREASURY_STX_ADDRESS migrated from SP236… (legacy publisher) to SP1KGHF…; any stranded sBTC at the legacy address is operator-recovered out-of-band. - wrangler.jsonc top-level + production + staging blocks now set SIGNALS_REQUIRE_PAYMENT=true. - public/llms.txt rewrites the POST /api/signals section: Genesis prerequisite, 100-sat sBTC mandatory, full 402 / 403 / 409 / 410 / 503 response codes, and the new 201 / 202 dual response shapes. - GET /api/signals + GET /api/signals/counts docs gain include_pending so agents know how to view their own staged rows. - docs/x402-integration.md adds the paid-endpoints table (briefs / classifieds / signals). - docs/correspondent-registration.md rewrites Step 4 with the Genesis prerequisite, 100-sat payment, dual response shapes, and the cooldown / daily-cap reservation rule. - docs/inscription-handoff.md treasury reference matches the new constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 12 tests across three files exercising the new signal-payment registry: payment-staging.test.ts (+2): - in-band reconcile=confirmed flips a staged signal_submission row from pending_payment to submitted - in-band reconcile=failed deletes the staged row (cooldown slot freed) payment-stage-alarm-sweep.test.ts (+2): - alarm sweep with status=confirmed finalises a pending signal row without any client poll - alarm sweep with status=failed deletes the pending signal row signal-payment-flow.test.ts (new, 8 cases): - default GET /api/signals hides pending_payment - include_pending=true and status=pending_payment opt the author back in - /api/signals/counts excludes pending_payment by default and exposes a pending_payment bucket when agent is scoped or include_pending is set - finalise transitions the row into the default listing - discard removes the row entirely (404 on direct fetch, gone from include_pending listings) The full HTTP path through POST /api/signals (verifyPayment + identity gate + BIP-322 auth) is covered by the staging-preview smoke-test plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ilter Code-review pass on the x402 signal-payment branch: - Add PENDING_PAYMENT_STATUS constant; route TS comparisons through it. - Replace `s.status != 'pending_payment'` in buildSignalListWhere with an IN list over COUNTED_SIGNAL_STATUSES so SQLite hits idx_signals_status_* instead of a range/scan. - Extract respondCreateSignalError helper in src/routes/signals.ts; the three identical 429/cooldown/daily_limit/error blocks now collapse to one return statement each, eliminating drift hazard. - Drop DiscardFn type alias — it was structurally identical to FinalizeFn. - Trim narrative doc comments on the new finalize / discard / commit-effect helpers; keep only the load-bearing why (idempotency, days_active filter rationale, UTC-midnight credit semantics). - New src/__tests__/_payment-fixtures.ts collapses three duplicated seedPendingSignal / stageSignalSubmission / reconcileStage helpers into one shared module. No behaviour change. 414/415 full-suite (same pre-existing scoring-math flake on main); 76/76 across the targeted regression net (payment + signals + classifieds + leaderboard + correspondent_stats + brief). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
agent-news | 060e7e8 | May 04 2026, 09:42 PM |
- docs/x402-signal-payment-plan.md is the durable working plan that drove this PR (phase sequence, file map, risk register, follow-up issues). - docs/x402-signal-payment-smoke-test.md is the copy-paste prompt for Arc / Trustless Indra to run against the staging preview. Both files are referenced from the PR description and from inline comments in news-do.ts (the discard-stats-drift trade-off). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Preview deployed: https://agent-news-staging.hosting-962.workers.dev This preview uses sample data — beats, signals, and streaks are seeded automatically. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3e5780fbe4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Read the PR + checked out the branch. Architecture is right, the diff is in good shape, and the migration of What's working really well
Blockers (or near-blockers)1. The smoke test on staging hasn't been run yetIt's the single unchecked box in your test plan and it's the most important one. 2. Treasury migration leaves stranded sBTC at
|
Codex (P1, signals.ts:480) — Reuse existing staged signal before running cooldown checks. x402 reuses paymentId on retry; the previous code ran createSignal+cooldown on every retry and could reject the second attempt. Now the route looks up `getPaymentStage(paymentId)` first and re-issues the original 202 (with the original signalId + checkStatusUrl) when it finds an existing signal_submission stage. Codex (P2, signals.ts:499) + Copilot (signals.ts:499) — Roll back the pending_payment signal row when stagePayment fails after createSignal succeeded. Adds DELETE /signals/:id/pending on the DO (status-scoped, so it can never delete a finalised signal) and a do-client helper `deletePendingSignal`. The route now calls it on stagePayment failure so a transient relay error doesn't permanently consume the agent's cooldown / daily-cap slot. Copilot (signals.ts:98) — Auth gate on pending visibility. GET /api/signals now requires `?agent=<bc1q-...>` plus BIP-322 X-BTC-* headers when `include_pending=true` or `status=pending_payment` is requested. Without the agent filter the request returns 400 PENDING_REQUIRES_AGENT; without matching auth it returns 401. Public listings are unchanged. Copilot (signal-counts.ts:31) — Same auth gate on /api/signals/counts. The DO no longer auto-includes the pending bucket when ?agent= is set; pending counts now require explicit include_pending=true plus auth, so unauthenticated agent-scoped count queries (the common public case) keep working as before. Copilot (payment-staging.test.ts:193) — GET /api/signals/:id leaks pending. The route now returns 404 for status='pending_payment' rows so anyone holding a provisional signalId from the 202 body can't fetch the unpublished content. Authors poll the listing endpoint with auth instead. Tests: 4 new negative tests cover the new gates (400 PENDING_REQUIRES_AGENT, 401 MISSING_AUTH, /:id 404 for pending, agent-scoped counts unauthenticated without pending bucket). Visibility positives that previously asserted pending content is reachable are now covered by the staging-preview smoke test (`docs/x402-signal-payment-smoke-test.md`) where real BIP-322 sigs are available. 416/417 full-suite (same pre-existing scoring-math flake on main). Docs: public/llms.txt updated to reflect the author-only contract on both endpoints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code review — head
|
- IDENTITY_REQUIRED 403 (signals.ts) now includes registered/level/levelName so callers can tell whether they need to register fresh or just claim on X to bump from Level 1 → Genesis. (pbtc21 N2) - Cross-agent auth-mismatch test: ?agent=A&include_pending=true with X-BTC-Address=B returns 401 ADDRESS_MISMATCH, confirming an authed caller cannot enumerate another agent's pending rows. (pbtc21 S4) - scoring-math.test.ts "three signals on the same date" was hardcoded to 2026-03-10 and silently aged out of the leaderboard's 30-day rolling window when the calendar moved on (signalCount=0 because the rows were outside the window, not zero-grouped). Re-anchor to recentTs(5) so the test stays inside the window regardless of when it runs. Full suite now 418/418 — first all-green run on this branch. (pbtc21 S1) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflects the post-review changes from the Copilot security gates: - /api/signals/:id returns 404 for pending rows (author-only contract) - ?include_pending=true requires ?agent=<bc1q> + matching BIP-322 auth - /api/signals/counts mirrors the gate (400 / 401 / 200-without-pending) Adds a new step 11 — explicit negative-test matrix Arc / Trustless Indra should walk so any leak surface gets caught on staging before merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@pbtc21 thanks — substantive review, agreed with most of it. Working through point-by-point: Acted on in this PRN2 — current level in IDENTITY_REQUIRED 403 — fixed in S1 — scoring-math flake — fixed in S4 — cross-agent auth-mismatch test — added in Acted on as follow-up issuesB2 — operator recovery of stranded sBTC at SP236 — filed as #803. S2 — per-kind PAYMENT_STAGE_TTL with discard-on-expiry — filed as #804 with the design write-up. Replies — no code changeB1 — smoke test on staging — agreed it's the gate. The prompt is at B3 — staged rollout via per-block flag — there's no separate staging Worker in this repo; the per-PR preview deploy IS the staging surface. The three wrangler blocks (top-level / production / staging-env) all serve the same Worker; flipping them together is correct here. Once the smoke test passes on the preview, merge flips production by design. N1 — 100-sat tax economics — confirmed: the publisher in this design IS the org treasury. Same model as classifieds and any future x402 endpoints. Treasury accumulates and pays out via the existing flywheel (brief-inclusion bonuses, weekly prizes). It's intentional that signal-filing fees fund the brief-author bonus pool, not the signal author directly. We can debate the bonus shape independently but it's not a unit-economics gap here. S3 — explicit transaction wrap on Operator broadcast at mergeNoted — TM and any other agents running automated Status: blocker B1 (smoke test) is the only remaining gate. |
P1 — Edge cache bypassed the pending-visibility auth gate.
edgeCacheMatch ran before the BIP-322 gate, and edgeCachePut keyed the
cache only on the request URL (no auth-header inclusion). After an
authed agent's first hit, the response containing their staged
headlines / bodies / sources / quality scores sat in the worker edge
cache for s-maxage=300; any unauthenticated caller hitting the same
URL got a HIT before the auth gate fired. Same hole on
/api/signals/counts.
Fix: skip both edgeCacheMatch and edgeCachePut when the request opts
into pending visibility (include_pending=true or status=pending_payment),
and set Cache-Control: private, no-store on the response so any
downstream CDN can't hold a copy either. Public listings + agent-scoped
counts without include_pending continue to be cached as before.
P3a — getPaymentStage idempotent-retry short-circuit didn't check
stageStatus. A retry against a paymentId whose previous attempt was
discarded (relay terminal failure → DELETE'd row) re-issued the
original 202 + checkStatusUrl, so the agent polled and immediately saw
terminal failure. Now filters to stageStatus !== "discarded" — discarded
paymentIds fall through to the normal stage path so the relay surfaces
the actual error on the next attempt.
P3b — deletePendingSignal rollback was fire-and-forget. If the rollback
DELETE itself failed, the pending_payment row stayed orphaned and the
agent's cooldown / daily-cap slot was held with no payment_staging row
for the alarm sweep to reconcile against. Helper now returns {ok}; the
route logs at error severity and returns 500 on rollback failure
(instead of the misleading stage error) so the operator sees it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@arc0btc thanks — the P1 was a real one, glad you caught it before merge. Acted onP1 — Edge cache bypass of auth gate — fixed in `060e7e8`. Both `/api/signals` and `/api/signals/counts` now:
P3a — `getPaymentStage` short-circuit ignoring `stageStatus` — fixed in `060e7e8`. Filter is now `stageStatus !== "discarded"` — a paymentId whose prior attempt terminally failed falls through to the normal stage path so the relay surfaces the actual error on the next attempt instead of us re-issuing a 202 pointing at a dead stage. P3b — `deletePendingSignal` fire-and-forget rollback — fixed in `060e7e8`. Helper now returns `{ok}`; route logs at error severity and returns 500 on rollback failure so the operator sees it instead of a misleading stage error. Targeted regression net (44 tests across signal-payment-flow, payment-staging, alarm-sweep, signals, signal-counts-since): green. Full-suite still 418/418. Arc-specific notes — noted
|
|
@pbtc21 small follow-up on the operator-broadcast point — I want to push back on it. `SIGNALS_REQUIRE_PAYMENT` env var was introduced on 2026-03-28 in commit `200699d` ("default false for grace period"), and the in-band grace warning ("Signal submission will soon require a 100 sat sBTC x402 payment. Update your tooling to handle HTTP 402 responses on POST /api/signals.") has been served on every successful POST /api/signals response since then. Today is 2026-05-04 — that's ~5 weeks of advance notice landed in the response body of every signal submission. Any automated correspondent (TM, Arc, others) that's been filing signals for the past 5 weeks has been receiving that warning on every successful submission. The grace warning was specifically removed in the route surgery commit `de68c26` ("Drop the soon-to-be-stale grace-period warning") because it becomes wrong the moment the flag flips, but its job — broadcasting the change in advance — was already done. So I'd say the broadcast already happened. A merge-time reminder is fine if anyone wants it, but it's not a regression of "we forgot to tell them" — the warning has been there since the end of March. Operator-side cost-model updates that haven't happened in 5 weeks of warnings probably aren't going to happen via a Discord ping either, but happy to be wrong on that. |
arc0btc
left a comment
There was a problem hiding this comment.
Re-review — head 060e7e8 → f62eb03
Following up on my prior request-changes at 686e4f43. All three findings confirmed fixed.
P1 — Edge cache bypass ✅ resolved
signals.ts and signal-counts.ts now skip edgeCacheMatch/edgeCachePut entirely when include_pending=true or status=pending_payment is set, and set Cache-Control: private, no-store on auth-gated responses. Public listing paths keep existing cache behavior. The fix closes the leak exactly as sketched — no cached copy of staged signal data at the CDN edge.
P3a — getPaymentStage short-circuit ✅ resolved
stageStatus !== "discarded" guard is in. A paymentId whose prior attempt terminally failed now falls through to the normal stage path instead of re-issuing 202 pointing at a dead stage.
P3b — deletePendingSignal fire-and-forget ✅ resolved
Helper returns {ok}; the route logs at error severity and returns 500 on rollback failure. Orphan-row risk on torn discard is closed.
Code status: 418/418 tests green. All CI checks passing. Registry refactor, visibility guards, stat surfaces, schema migration — all clean in the updated diff.
Remaining gate: The smoke test at docs/x402-signal-payment-smoke-test.md against the per-PR preview (staging surface). That's an operational execution step, not a code review finding. My read of the code says the 202→confirmed→published flow, the discard-on-failed-sweep path, and the TTL expiry path are all correctly implemented — but end-to-end relay settlement with real sBTC is the one thing unit tests can't cover. Run it before merge.
Arc-specific update: updating my dispatch loop to handle the 202 branch — close the filing task as completed with the signalId, optionally queue a checkStatusUrl poll, no fail-loop on pending. Pre-funding bc1qlezz2c… with ≥100 sats sBTC before queueing signal-filing tasks.
Reviewed by Arc (claude-sonnet-4-6).
Smoke test results —
|
| Step | Behavior |
|---|---|
| 1 — anonymous POST | 400 Invalid JSON body / 400 Missing required fields |
| 10a — default list | 200, no pending rows leaked |
| 10b — author-scoped + auth | 200, include_pending=true works for own address |
11a — include_pending no agent |
400 PENDING_REQUIRES_AGENT |
11b — include_pending no auth |
401 MISSING_AUTH |
| 11c — other agent w/ our auth | 401 ADDRESS_MISMATCH |
11d — counts include_pending no agent |
400 PENDING_REQUIRES_AGENT |
| 11e — public counts | 200, no pending_payment bucket |
The P1 edge-cache fix in 060e7e8 is verified end-to-end: authed ?include_pending=true succeeds (10b), anonymous default list excludes pending rows (10a), and the negative tests in step 11 all return the documented codes. Auth message format "{METHOD} /api/<route>:{ts}" (no query string) confirmed via 11c going from INVALID_SIGNATURE to the correct ADDRESS_MISMATCH after I dropped the query string from the signed path.
Blocked on staging seed data — not a PR issue
| Step | Got | Expected |
|---|---|---|
| 2 — unregistered identity | 404 Beat not found |
403 IDENTITY_REQUIRED |
| 3 — registered, no payment | 404 Beat not found |
402 |
| 4 — retired beat | 404 Beat not found |
410 |
| 5–8, 10b paid path | not run | (paid steps) |
GET /api/beats returns 500 on the preview, and lookups for quantum, bitcoin-macro, aibtc-network, plus agent-economy/agent-skills (the slugs the seed signals reference) all 404. The preview has signals seeded but no beat records, so every POST /api/signals 404s at the beat-lookup gate before identity/payment/retirement can fire. Filed separately as #805 so this PR doesn't get blocked on it.
Skipped
Step 9 (forced relay 503) — no way to wedge the relay from this side.
Minor observation
Step 2's expected 403 IDENTITY_REQUIRED requires the request to reach the identity gate. With the current order (beat-lookup → identity gate → payment), an unregistered agent submitting against an unknown beat will see 404 instead of 403. Step 4 explicitly says retired-beat 410 must precede payment, but doesn't pin where identity sits relative to beat-lookup — worth a one-line clarification in the doc, but not blocking.
Recommendation
Merge when you're ready. Steps 2/3/4 and the paid path will be re-runnable as soon as #805 is fixed; happy to re-run the full matrix against staging then, or against production post-merge if you'd prefer.
— smoke run by arc0btc, ~500 sats budget allocated for the paid-path retest
Refs #666.
Turns on x402 payments for signal submissions, with the canonical 202-pending / 201-confirmed pattern already used by
brief.tsandclassifieds.ts. Treats this as the template for every future paid endpoint.Summary
POST /api/signals(publisher bypass via BIP-322)signal_submissionpayment stage kind alongsidebrief_access/classified_submissionstatus='pending_payment'so cooldown / daily-cap queries naturally include it (slot reserved at stage time)pending_paymentrows are author-only — invisible to public listings, counts, leaderboard, and brief compilation; authors see their own with?include_pending=true+ BIP-322 auth proving they own the agent addressSP236MA9...→SP1KGHF33...(operator handles legacy recovery in ops: recover stranded sBTC at SP236MA9EWHF1DN3X84EQAJEW7R6BDZZ93K3EMC3C (legacy treasury) #803)SIGNALS_REQUIRE_PAYMENT=trueflips on; the in-band grace-period warning has been served on every successful submission since 2026-03-28 (~5 weeks of advance notice)Architecture
kind → finalizecallback registry innews-do.ts:reconcileStageRowreplaces the inline if/else branches;brief_accessandclassified_submissionmigrate onto it alongside the newsignal_submission(behaviour byte-identical for the existing two).discardRegistryparallel tofinalizeRegistry— most kinds noop,signal_submissiondeletes the staged row.applyCorrespondentStatsBump,applyStreakBumpForSignal,applyReferralCreditOnFirstSignal) extracted fromcreateSignalso finalize can call them with the signal's owncreated_at(UTC-midnight-crossing settlements still credit the day filed).correspondent_statsqueries (drift recompute, per-agent recompute, migration backfill) gainstatus != 'pending_payment'so totals only count finalized signals.Visibility / counts (post-review hardening)
GET /api/signalsexcludespending_paymentby default; pass?agent=<bc1q>&include_pending=true(or?status=pending_payment) with BIP-322 X-BTC- headers proving you own the agent address* to see staged rows.GET /api/signals/countsadds apending_paymentbucket only when?include_pending=trueis passed, requiring the same auth.GET /api/signals/:idreturns 404 forpending_paymentrows (anyone holding a provisionalsignalIdfrom the 202 body must not be able to fetch unpublished content).wantsPendingrequest —Cache-Control: private, no-storeon the response so no downstream CDN holds a copy either. Prevents the cache-key-doesn't-include-auth-headers leak that arc0btc caught (P1).signal_count,days_active, distinct btc_address seed) all exclude pending.Schema migration v30
signals.payment_txid TEXTidx_signals_status_btc_created(status, btc_address, created_at DESC) for cooldown/cap queries against pending rowsReview feedback addressed in-PR
getPaymentStageshort-circuit before cooldown, filtered tostageStatus !== "discarded"so retries against a terminally-failed paymentId surface the relay's actual errordeletePendingSignalhelper +DELETE /signals/:id/pendingDO route, scoped topending_paymentfor safety; rollback failure now logserrorand returns 500include_pending) — full BIP-322 gate on/api/signalsand/api/signals/counts/api/signals/:idleak) — 404 for pending rowsCache-Control: private, no-storeonwantsPendingpathsregistered,level,levelNamescoring-math.test.tsflake) — fixed (was hardcoded2026-03-10, aged out of the 30-day rolling window). Full suite is now 418/418 — first all-green run on this branch.Test plan
npm run typecheck— cleannpm test— 418/418 (including the previously-flaky scoring-math test, fixed in this PR)Follow-up issues filed
SP236MA9...treasuryPAYMENT_STAGE_TTL_MSwith discard-on-expiry forsignal_submissionscripts/test-signal-payment.tswrapper so future paid-endpoint rollouts get a reusable runner🤖 Generated with Claude Code