Skip to content

Commit a5414a5

Browse files
committed
The day the canvas claim flipped: README, site, deck, DX-9/10/11
Every judge-facing surface said the workflow does not exist on the canvas. That was true until 2026-09-11 08:06 UTC and false after. Rewritten with the dated facts — 7 canvas runs, 2 executions, 2 refusals, 1 GS026 loss, Marketplace listing gavel-drain at $0.05 with zero external paid calls — and a correction row so the flip is readable without git log. The frozen DoraHacks Q2 answer still carries the old claim and cannot be edited. Three findings from the first canvas day, in DX-REPORT.md: - DX-9 matchesRegex is documented, generated, and rejected by the validator - DX-10 create stores workflowType=read regardless of nodes; PATCH derives - DX-11 failOnError:false reports a reverted write as success: true
1 parent f086cfc commit a5414a5

4 files changed

Lines changed: 130 additions & 37 deletions

File tree

‎DX-REPORT.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,83 @@ records what executed structurally cannot record what did not.
317317
failed runs with enough identity to be counted even without a transaction hash. If retention is
318318
plan-dependent, say which plan buys what — this is the one endpoint whose whole value is that it
319319
remembers.
320+
321+
## DX-9 · `matchesRegex` is documented, generated by the builder, and rejected by the validator
322+
323+
**Severity:** medium — a documented Condition operator cannot be used at all; the error message
324+
points somewhere else.
325+
**Date:** 2026-09-11 · execution `4y5c1zst9hoegqkybyhvj`
326+
327+
`docs/workflows/creating.md` lists `matchesRegex` among the Condition operators. Our address gate
328+
used it: `{{@trigger-1:Webhook.safeAddress}} matchesRegex ^0x[0-9a-fA-F]{40}$`. The first canvas
329+
run failed at that node with *"Cannot index "0x[...]". Reference step outputs with the
330+
{{@nodeId:Label.field}} template format…"* — advice about template syntax, for an expression
331+
whose template syntax was fine.
332+
333+
Reading `lib/workflow/nodes/condition/validator.ts` explains why no spelling works:
334+
335+
- `checkBracketExpressions` matches `(\w+)\s*\[` anywhere in the raw expression, **inside string
336+
literals included**, so any character class preceded by a word character (`0x[`, `a-f[`) is
337+
rejected as bare indexing.
338+
- The builder compiles the operator to `new RegExp("…").test(String(…))`
339+
(`condition/expression.ts:120`), but `DANGEROUS_PATTERNS` contains `/\bnew\s+\w/` and
340+
`ALLOWED_METHODS` has no `test`. The compiled form is banned twice.
341+
342+
So the operator exists in the docs and the UI, and every expression it produces fails validation.
343+
The tests in `tests/unit/condition-builder-utils.test.ts` cover generation and parsing, never
344+
evaluation.
345+
346+
**Workaround:** `startsWith("0x") && .length === 42`, with the strict regex in the Code node.
347+
**Suggested fix:** mask string literals before the bracket and dangerous-pattern checks, and either
348+
allowlist `new RegExp(...)`/`.test` for this operator or evaluate `matchesRegex` natively instead of
349+
compiling it to JS. Until then, remove it from the operator list — an operator that is documented
350+
and unusable costs more than one that is absent.
351+
352+
## DX-10 · `workflows/create` stores `workflowType: read` regardless of nodes; only `PATCH` derives
353+
354+
**Severity:** medium — combined with #2227's freeze-at-listing, it is a trap with a misleading
355+
warning in front of it.
356+
**Date:** 2026-09-11 · workflow `7v0qwhcp5gcex58gjugyg`
357+
358+
`POST /api/workflows/create` with a graph containing a `web3/write-contract` node returned a row
359+
with `workflowType: "read"`. `validate_workflow` then warned `write-action-on-read-workflow` —
360+
"confirm this is intentional" — on a workflow nobody had classified. A no-op
361+
`PATCH /api/workflows/{id}` with the same nodes flipped it to `write`, because the PATCH route
362+
derives from node content (#2227) and the create route does not.
363+
364+
Why it matters: #2227 freezes `workflowType` at the listing PATCH. A workflow created and listed
365+
without an intermediate save — the obvious agent flow, `create_workflow` → `list_workflow` — lists
366+
as `read`, and the Marketplace call then routes to `handleReadWorkflow`: the server executes with
367+
the **owner's** wallet and returns results, instead of returning calldata for the caller to sign.
368+
For a workflow whose whole point is that the caller executes, that is the wrong contract, frozen.
369+
370+
**Suggested fix:** derive on create exactly as on PATCH, or have `validate_workflow` say *"stored
371+
type was never derived — save once"* rather than asking the author to confirm a classification
372+
they did not make.
373+
374+
## DX-11 · `failOnError: false` turns a reverted write into `success: true` at every level
375+
376+
**Severity:** high for observability — the one outcome an executor most needs to see is the one
377+
that reports as success.
378+
**Date:** 2026-09-11 · executions `819zav3gl3fkd89n7cb42` (won) and `j9g0a58oxem50jct5qpt7` (lost)
379+
380+
Two concurrent triggers of the same workflow against the same Safe nonce. One broadcast and moved
381+
the money; the other's `execTransaction` reverted with `Error(GS026)`. The loser's `exec-1` node
382+
log reads:
383+
384+
```
385+
{"error": "Contract call failed: Error(GS026)", "success": true,
386+
"rejection": {"kind": "contract-custom", "name": "Error"}}
387+
```
388+
389+
— `success: true` beside an `error`, node status `success`, execution status `success`,
390+
`transactionHashes: []`. The workflow's own reporting cannot distinguish "drained the Safe" from
391+
"reverted against it" without opening the node output and reading the error string. `failOnError:
392+
false` is the right setting here — a Safe revert is a named outcome, not a dead run — but the flag
393+
should suppress *abort*, not *truth*.
394+
395+
**Suggested fix:** keep `success` meaning the call succeeded; surface the swallowed error as a
396+
distinct node status (`failed-continued`, or `status: success` + `error` populated at the
397+
execution level), and count the run in `analytics/runs` with a `revertReason` even though it has
398+
no hash. DX-8 already notes that a failed run leaves no row; this is the same gap from the other
399+
side.

0 commit comments

Comments
 (0)