1
2
return {
3
title = "Upgrade a free account to the Pro plan via the dashboard",
4
priority = "P0",
5
tags = {"billing", "checkout"},
6
policy = { capabilities = {"http", "browser", "intent", "judge", "property"} },
7
8
run = function()
9
10
local signup = sigil.post("/api/signup", {
11
email = sigil.gen.email(),
A
12
password = sigil.env("SIGNUP_PASSWORD"),
B
13
})
14
expect(signup.status == 201)
15
local token = signup.json.session_token
16
17
18
19
20
21
--- Upgrade the account to the Pro plan (annual billing, $99/yr).
22
--- Use the test card 4242 4242 4242 4242, any future expiry, any CVC.
23
--- Confirm the upgrade completed and record the confirmation details.
24
local result = sigil.intent({
D
25
capabilities = { "browser", "http" },
26
context = { session_token = token },
27
capture = {
28
order_id = "string: the order confirmation number",
29
total_cents = "number: the final charged amount in cents",
30
plan = "string: the plan name shown on the receipt",
31
},
32
max_steps = 20,
33
})
34
expect(result.completed)
35
expect(result.plan == "Pro")
36
expect(result.total_cents == 9900)
C
37
38
39
40
sigil.browser.open("/account/billing")
E
41
sigil.browser.wait({ text = "Pro" })
42
expect(sigil.browser.text("[data-testid=plan-badge]") == "Pro")
43
expect(sigil.browser.visible("[data-testid=cancel-plan]"))
44
45
46
local sub = sigil.get("/api/subscription", nil, {
F
47
headers = { Authorization = "Bearer " .. token },
48
})
49
expect(sub.json.plan == "pro")
50
expect(sub.json.status == "active")
51
52
53
54
invariant("GET /api/subscription is idempotent", {
G
55
cases = 10,
56
for_all = { req_id = sigil.gen.uuid() },
57
check = function(case)
58
local r = sigil.get("/api/subscription", nil, {
59
headers = { Authorization = "Bearer " .. token, ["X-Request-Id"] = case.req_id },
60
})
61
expect(r.json.plan == "pro")
62
end,
63
})
64
65
--- The receipt email copy is clear, professional, and free of typos.
66
--- It names the correct plan, the correct price, and the next billing date.
67
sigil.judge(sub.json.receipt_preview, { min_score = 0.85 })
H
68
end,
69
}
A sigil.gen.* line 11
generators
Deterministic value generators seeded from the scenario RNG. Emails, UUIDs, ints, strings — every call is reproducible across runs, so property tests, fuzzers, and replays all share one seed chain.
B sigil.env() line 12
env access
Typed access to an evaluator-scoped secret vault. Values are read from the locked environment, never inlined into the scenario source, and never echoed into feedback or the ledger.
C expect line 36
power assertions
Comparisons inside expect are rewritten to capture both sides. On failure, the renderer prints an Ariadne code frame with the value each sub-expression resolved to — this line is the one that fails in §06.
D sigil.intent line 24
agent instruction
Plain-English objective in the `---` block. The LLM drives the declared capabilities via tool-use; typed capture fields become the completion schema, so the agent must return structured values.
E sigil.browser line 40
browser automation
Getters (text, url, visible) return strings; actions (open, click, fill, wait) return nil. Sessions auto-isolate per scenario ID — no cookie bleed between parallel runs.
F sigil.get line 46
http calls
Typed HTTP against the deployed service. Request and response metadata, headers, and JSON bodies are surfaced structured — not as raw strings you have to parse back out.
G invariant line 54
property testing
Generate N cases from the declared generators, run the check against each, and shrink counter-examples on failure. A claim about the service, not a sequence of steps.
H sigil.judge line 67
llm rubric
Plain-English rubric in the preceding `---` block becomes the grading criteria for a Tier-3 judge. The score and rubric digest land in the ledger; the prompt never leaves the evaluator.