Skip to content

release: to prod#1267

Merged
joelorzet merged 20 commits into
prodfrom
staging
May 15, 2026
Merged

release: to prod#1267
joelorzet merged 20 commits into
prodfrom
staging

Conversation

@joelorzet
Copy link
Copy Markdown

No description provided.

joelorzet and others added 6 commits May 14, 2026 18:22
…keystroke

The Manual ABI text area lost focus after 1-2 characters because the
TemplateBadgeTextarea's React key included `value?.length || 0`. Every
keystroke changed the value length, which changed the key, which made
React unmount and remount the textarea -- killing focus mid-word.
Pasting "worked" only because it's a single change.

Drop `value.length` from the key. The textarea's internal effect already
syncs parent-driven `value` changes on blur, and the toggle-driven parts
of the key (`useProxyAbi`, `useDiamondAbi`) still trigger the remount
that proxy/diamond source switches actually need.
Hard-deleting a workflow removed its listed_slug from the
idx_workflows_listed_slug unique index, letting any other workflow
immediately re-claim the freed slug and inherit its traffic + x402
revenue.

Workflows are now soft-deleted: the delete handler sets deleted_at
(and is_listed = false) instead of deleting the row, so the row keeps
its slug bound in the unique index forever. The index is intentionally
unchanged -- the surviving row is what blocks re-claim. Execution
history is still hard-deleted on force; schedules are removed
explicitly since the ON DELETE CASCADE no longer fires on soft-delete.

All workflow reads are audited to filter deleted_at IS NULL: public
listing, payment binding, slug call route, catalog, x402 discovery,
public feed, and executor lookups. Mutation/execution/export routes
reject deleted workflows. The owner-facing workflow list deliberately
keeps showing soft-deleted rows, marked "Deleted" in the sidebar, so
the user has an audit trail and recovery window.

Stat/analytics surfaces (metrics, leaderboard, earnings) are not yet
filtered -- tracked as follow-up; they are correctness-of-stats, not a
security boundary.
Review found trigger-driven execution paths and a discovery surface the
first pass missed -- all places that resolve a workflow without a
deleted_at filter:

- webhook route: a soft-deleted workflow was still executable via its
  webhook URL (enabled is not cleared on delete, only is_listed).
- internal/executions: created a running execution row for a
  soft-deleted workflow with no deleted_at check.
- events + block-workflows internal endpoints: handed soft-deleted
  event/block-trigger workflows to the worker services, which would
  keep firing executions for them.
- openapi discovery doc: published soft-deleted slugs to external
  agents (relied on is_listed=false coupling instead of an explicit
  filter, like the sibling x402/catalog routes already do).
- execution-status route: now hides a soft-deleted workflow's
  execution detail, matching the execution-history route.
- hub protocol counts: excluded soft-deleted workflows so public
  counts stay accurate.

Also extracts softDeleteValues() so the delete handler and the
duplicate-route anonymous move write the same { deletedAt, isListed:
false } shape -- no more divergent half-retired states.
POST /api/agentic-wallet/feedback went straight from calldata to
estimateGas with no check of the caller wallet's ETH balance. The
caller wallet pays mainnet gas natively, so a wallet with zero ETH
cannot complete the giveFeedback() tx -- but instead of a clean
error, that case failed opaquely (observed as a Cloudflare 502 with
no usable body) because estimateGas on a 0-balance account can stall
or error unhelpfully on some RPC providers.

buildAndSignTx now reads the wallet balance up front:
  - zero balance short-circuits before estimateGas is ever called
  - a non-zero balance is re-checked against the real estimated cost
    (gasWithHeadroom * maxFeePerGas) once it is known

Either case throws InsufficientGasError, which the POST handler
surfaces as 402 INSUFFICIENT_GAS with balanceWei (and requiredWei
when known). The feedback row is still marked "failed" with a null
txHash, so it stays a stranded, retryable row -- the caller funds
the wallet and retries, recovering via the existing KEEP-515 path.

Adds integration coverage for both the zero-balance and
balance-too-low branches.
… red

Previous defaults left a 10-minute gap between a chain going silent and
the dispatcher flipping to fallback (5min BLOCK_ADVANCE_TIMEOUT x 2
SILENT_FAILOVER_THRESHOLD). The Grafana dashboard cell turned red at
120s and the Block Dispatcher Chain Silent alert finished its for=2m
debounce at the same point — but the dispatcher itself was still
sitting on the broken primary for another 8 minutes.

Lowers BLOCK_ADVANCE_TIMEOUT_MS from 5min to 60s. With the unchanged
SILENT_FAILOVER_THRESHOLD=2 the flip now happens at 120s, aligned with
both the dashboard threshold and the alert firing.

60s is the floor that keeps Ethereum's 12s block time tolerable
during testnet variance (5 missed blocks = noisy but not unusual) while
making the silent-subscription failure mode detectable on sub-2s
chains (Polygon, Base, Avalanche, BSC) within tens of expected blocks
instead of hundreds.

Also tightens MONITOR_RECREATE_TIMEOUT_MS from 10min to 120s so the
reconciler's tear-down-and-recreate backstop runs at the same window.
By the time the reconciler hits this gate, the chain has already had
its first reconnect attempt and is about to flip; the recreate covers
monitors that wedge during the flip itself.

Tests: the "stays alive for 10 minutes" reaper test now pins
MONITOR_RECREATE_TIMEOUT_MS via vi.stubEnv so the test stays decoupled
from the production default. All 80 unit tests pass.

Operationally this still has zero false-positive risk: the actual flip
only happens after the chain has been silent past 2x the timeout AND
the in-process reconnect on the same URL also failed to advance the
height — i.e. it's two consecutive 60s windows of nothing arriving.
…failover-thresholds

fix(block-dispatcher): tighten failover thresholds to match dashboard red
…flight

fix: add ETH-balance gas preflight to agentic-wallet feedback route
…g-soft-delete

fix: KEEP-440 soft-delete workflows to close slug-squat-after-delete
joelorzet added 4 commits May 15, 2026 11:33
…nce and broader edge cases

Adds two new test areas across unit, integration, and E2E layers:

1. Node-count invariance -- explicitly verifies the validator does not
   branch on node-count or index position. The user reported that saves
   "fail when there are more than 2 nodes." That observation is
   coincidental (1-2 node workflows usually have no contract action);
   the validator iterates every node uniformly. The new tests prove it
   by saving workflows of size 1, 2, 3, 5, 10, 20 with the affected
   node at varying indices, and asserting:
   - all sizes succeed when the affected node is valid
   - invalidFields report the correct nodes[N] path regardless of N
   - multiple affected nodes can be reported in one call
   - no aggregate cap exists

2. Broader save-time edge cases unrelated to KEEP-571 -- coverage we
   were missing on the validator: empty nodes array, trigger-only
   payloads, missing/blank actionType, RESERVED_CONFIG_KEYS pass-
   through, numeric values on string-like fields, object values
   rejected on string-like fields, showWhen gating both ways,
   per-node isolation of failures, deterministic issue ordering,
   integer-string on protocol-uint, empty-string treated as missing
   on required fields.

Counts:
- unit: 32 -> 60 tests (+28)
- integration: 28 -> 35 tests (+7)
- e2e: 5 -> 10 tests (+5)
Single formatter-only fix in chain-monitor.ts: line-wrap on the
stalenessBaseline / MONITOR_RECREATE_TIMEOUT_MS comparison collapsed
onto one line per Biome rules.

No logic change. 81/81 scheduler unit tests still pass, typecheck clean.
Resolves the lint failure blocking #1272.
…ction key leaks

Cross-workspace workflow imports were rejected on save by the new
action-config validator when the imported nodes carried two classes of
keys the validator did not recognize:

1. useManualAbi -- a UI-only boolean toggle written by the
   abi-with-auto-fetch field renderer
   (components/workflow/config/abi-with-auto-fetch-field.tsx). It is
   persisted on every action that uses that field type so the form
   remembers the user's manual-vs-fetched ABI choice across reloads.
   The step runtime never reads it.

2. inputMode / batchSize on web3/query-transactions and
   web3/query-events -- leftover form state from a sibling action
   (batch-read-contract declares these as real fields). When a user
   started authoring as batch-read-contract and switched the action
   type, the prior keys persisted. The runtimes for query-transactions
   and query-events hardcode their own batching strategy and ignore
   these keys.

Validator changes:
- Add useManualAbi to RESERVED_CONFIG_KEYS (global, since it can
  appear on any action with abi-with-auto-fetch).
- Introduce LEGACY_IGNORED_FIELDS: per-action allowlist of keys that
  do not map to a canonical field but must not be rejected (legacy /
  cross-action leftovers). Seed it with query-transactions and
  query-events accepting inputMode and batchSize. Truly unknown keys
  are still rejected, and batch-read-contract's own inputMode is
  still type-checked.

Test additions (unit + integration):
- useManualAbi accepted on every web3 action with abi-with-auto-fetch
  (read, write, query-events, query-transactions, batch-read-contract)
- inputMode/batchSize accepted as cross-action leaks on
  query-transactions and query-events
- Combined payload (useManualAbi + inputMode + batchSize) accepted on
  query-transactions
- Truly unknown field still rejected when useManualAbi is present
- inputMode is still type-validated on batch-read-contract where it
  is a real field
- Integration tests for the same shapes at the PATCH route boundary

Final tally: 108 unit + integration tests passing on this branch.

Test data hygiene: all artifact names in tests are mocked
(reader/doWrite/idBytes32/Prev Loop/targetAddress) -- no
customer-specific debug data lands in the repo.
…cept-stringified-args

fix(workflow-save): KEEP-571 accept stringified container fields and legacy aliases
…tuck-monitor

fix: KEEP-570 reaper now detects monitors stuck across silent reconnects
…rea-focus

fix(workflow): KEEP-487 stop remounting manual ABI textarea on every keystroke
@joelorzet joelorzet merged commit f990116 into prod May 15, 2026
38 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.

3 participants