explorbot 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/explorbot-cli.ts +26 -8
- package/boat/api-tester/src/cli.ts +17 -0
- package/boat/api-tester/src/config.ts +4 -2
- package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
- package/boat/doc-collector/src/ai/documentarian.ts +61 -31
- package/boat/doc-collector/src/cli.ts +14 -1
- package/boat/doc-collector/src/config.ts +4 -2
- package/boat/prima/bin/prima-cli.ts +0 -0
- package/boat/prima/src/activity-line.ts +33 -0
- package/boat/prima/src/cli.ts +127 -86
- package/boat/prima/src/envelope.ts +102 -52
- package/boat/prima/src/prima.ts +567 -128
- package/boat/prima/src/pw-parser.ts +11 -1
- package/boat/prima/src/pw-registry.ts +4 -5
- package/boat/prima/src/session-log.ts +126 -0
- package/dist/bin/explorbot-cli.js +26 -8
- package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
- package/dist/boat/api-tester/src/cli.js +17 -0
- package/dist/boat/api-tester/src/config.js +4 -2
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
- package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
- package/dist/boat/doc-collector/src/cli.js +14 -1
- package/dist/boat/doc-collector/src/config.js +4 -2
- package/dist/boat/prima/src/activity-line.js +30 -0
- package/dist/boat/prima/src/cli.js +109 -77
- package/dist/boat/prima/src/envelope.js +94 -44
- package/dist/boat/prima/src/prima.js +533 -119
- package/dist/boat/prima/src/pw-parser.js +13 -1
- package/dist/boat/prima/src/pw-registry.js +4 -5
- package/dist/boat/prima/src/session-log.js +108 -0
- package/dist/package.json +3 -2
- package/dist/rules/navigator/verification-actions.md +20 -0
- package/dist/src/action-result.d.ts +7 -0
- package/dist/src/action-result.js +4 -0
- package/dist/src/action.d.ts +2 -0
- package/dist/src/action.js +41 -2
- package/dist/src/ai/captain/web-mode.js +6 -3
- package/dist/src/ai/captain.js +2 -0
- package/dist/src/ai/navigator.d.ts +34 -0
- package/dist/src/ai/navigator.js +237 -181
- package/dist/src/ai/pilot.d.ts +7 -0
- package/dist/src/ai/pilot.js +90 -2
- package/dist/src/ai/provider.d.ts +2 -2
- package/dist/src/ai/provider.js +14 -23
- package/dist/src/ai/rerunner.js +2 -1
- package/dist/src/ai/researcher/cache.d.ts +2 -0
- package/dist/src/ai/researcher/cache.js +10 -2
- package/dist/src/ai/researcher.js +3 -2
- package/dist/src/ai/rules.js +17 -10
- package/dist/src/ai/session-analyst.js +2 -0
- package/dist/src/ai/task-agent.js +4 -1
- package/dist/src/ai/tester.d.ts +6 -3
- package/dist/src/ai/tester.js +50 -46
- package/dist/src/ai/tools.d.ts +14 -0
- package/dist/src/ai/tools.js +117 -37
- package/dist/src/commands/config-command.d.ts +51 -0
- package/dist/src/commands/config-command.js +117 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/config.d.ts +9 -1
- package/dist/src/config.js +53 -4
- package/dist/src/execution-controller.d.ts +2 -0
- package/dist/src/execution-controller.js +6 -0
- package/dist/src/explorbot.d.ts +2 -1
- package/dist/src/explorbot.js +7 -2
- package/dist/src/explorer.js +2 -3
- package/dist/src/playwright-recorder.js +30 -0
- package/dist/src/remote.d.ts +55 -0
- package/dist/src/remote.js +235 -0
- package/dist/src/reporter.d.ts +1 -0
- package/dist/src/reporter.js +7 -1
- package/dist/src/state-manager.d.ts +2 -1
- package/dist/src/state-manager.js +3 -1
- package/dist/src/stats.d.ts +1 -0
- package/dist/src/stats.js +1 -0
- package/dist/src/test-plan.d.ts +3 -0
- package/dist/src/test-plan.js +26 -0
- package/dist/src/utils/aria.d.ts +2 -8
- package/dist/src/utils/aria.js +69 -40
- package/dist/src/utils/html.js +1 -0
- package/dist/src/utils/logger.d.ts +7 -1
- package/dist/src/utils/logger.js +32 -0
- package/dist/src/utils/page-readiness.js +18 -1
- package/dist/src/utils/url-matcher.js +3 -0
- package/dist/src/utils/web-element.d.ts +2 -0
- package/dist/src/utils/web-element.js +8 -0
- package/dist/src/utils/web-sandbox.d.ts +1 -1
- package/dist/src/utils/web-sandbox.js +2 -3
- package/docs/api-testing/basics.md +90 -0
- package/docs/api-testing/planning.md +57 -0
- package/docs/api-testing/running-tests.md +55 -0
- package/docs/assets/cloud-report.png +0 -0
- package/docs/assets/html-report.png +0 -0
- package/docs/assets/langfuse-trace.png +0 -0
- package/docs/assets/successful-explore-run.png +0 -0
- package/docs/basics/getting-started.md +140 -0
- package/docs/basics/prerequisites.md +63 -0
- package/docs/basics/providers.md +362 -0
- package/docs/basics/running.md +78 -0
- package/docs/contributing/ai-integration-tests.md +57 -0
- package/docs/contributing/contributing.md +90 -0
- package/docs/contributing/demo-videos.md +36 -0
- package/docs/contributing/npm-package.md +138 -0
- package/docs/contributing/observability.md +227 -0
- package/docs/contributing/regression-tests.md +103 -0
- package/docs/contributing/testing.md +95 -0
- package/docs/doc-collection/basics.md +128 -0
- package/docs/doc-collection/crawling.md +67 -0
- package/docs/doc-collection/interactive-mode.md +99 -0
- package/docs/index.json +87 -0
- package/docs/reference/commands.md +997 -0
- package/docs/reference/configuration.md +569 -0
- package/docs/reference/scripting.md +303 -0
- package/docs/reference/websocket.md +50 -0
- package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
- package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
- package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
- package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
- package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
- package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
- package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
- package/docs/superpowers/specs/2026-08-18-prima-false-verdicts.md +159 -0
- package/docs/web-testing/agents.md +158 -0
- package/docs/web-testing/automated-tests.md +134 -0
- package/docs/web-testing/basics.md +91 -0
- package/docs/web-testing/customization.md +131 -0
- package/docs/web-testing/hooks.md +238 -0
- package/docs/web-testing/page-interaction.md +84 -0
- package/docs/web-testing/planner.md +122 -0
- package/docs/web-testing/rerun.md +164 -0
- package/docs/web-testing/researcher.md +380 -0
- package/docs/workflow/agentic-usage.md +233 -0
- package/docs/workflow/application-spec.md +73 -0
- package/docs/workflow/ci.md +202 -0
- package/docs/workflow/knowledge.md +310 -0
- package/docs/workflow/planning-styles.md +67 -0
- package/docs/workflow/reporting.md +133 -0
- package/docs/workflow/test-plans.md +90 -0
- package/package.json +3 -2
- package/rules/navigator/verification-actions.md +20 -0
- package/src/action-result.ts +11 -0
- package/src/action.ts +43 -3
- package/src/ai/captain/web-mode.ts +6 -3
- package/src/ai/captain.ts +3 -0
- package/src/ai/navigator.ts +255 -186
- package/src/ai/pilot.ts +104 -2
- package/src/ai/provider.ts +14 -24
- package/src/ai/rerunner.ts +2 -1
- package/src/ai/researcher/cache.ts +12 -2
- package/src/ai/researcher.ts +3 -2
- package/src/ai/rules.ts +17 -10
- package/src/ai/session-analyst.ts +2 -0
- package/src/ai/task-agent.ts +3 -1
- package/src/ai/tester.ts +52 -45
- package/src/ai/tools.ts +136 -37
- package/src/commands/config-command.ts +146 -0
- package/src/commands/index.ts +2 -0
- package/src/config.ts +60 -5
- package/src/execution-controller.ts +8 -0
- package/src/explorbot.ts +7 -3
- package/src/explorer.ts +2 -2
- package/src/playwright-recorder.ts +23 -0
- package/src/remote.ts +244 -0
- package/src/reporter.ts +7 -1
- package/src/state-manager.ts +6 -2
- package/src/stats.ts +1 -0
- package/src/test-plan.ts +29 -0
- package/src/utils/aria.ts +65 -45
- package/src/utils/html.ts +1 -0
- package/src/utils/logger.ts +33 -2
- package/src/utils/page-readiness.ts +24 -1
- package/src/utils/url-matcher.ts +3 -0
- package/src/utils/web-element.ts +9 -0
- package/src/utils/web-sandbox.ts +3 -4
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Prima Fixes — Perception Ladder, No Heal, Proof-Carrying Envelopes
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-07
|
|
4
|
+
**Status:** Draft for review
|
|
5
|
+
**Supersedes parts of:** `2026-08-01-prima-boat-design.md`
|
|
6
|
+
**Evidence:** `docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md`
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
A field run of prima against a live app (Testeiya, `localhost:3050`) found the boat working but
|
|
11
|
+
not trustworthy as an executor. Four defects matter:
|
|
12
|
+
|
|
13
|
+
- A `pw` call on a selector that does not exist returned `ok: true`, `healed: true`, having
|
|
14
|
+
clicked an unrelated control ("New agent"). Heal substituted the target and reported success.
|
|
15
|
+
- `### Changes` never appeared in any successful envelope, so a successful action proved
|
|
16
|
+
nothing and every step had to be confirmed with a playwright-cli snapshot.
|
|
17
|
+
- `do` given two instructions performed four, typing into a live chat box actions nobody asked
|
|
18
|
+
for.
|
|
19
|
+
- Auto-discovery of a playwright-cli session never matched, while the failure message advised
|
|
20
|
+
opening the session the user already had open.
|
|
21
|
+
|
|
22
|
+
Underneath the third defect is a perception problem. `action.ts:139` captures
|
|
23
|
+
`page.locator('body').ariaSnapshot()` **without** `mode: 'ai'`, so `do` sees roles and text but
|
|
24
|
+
no element handles and must invent locators. That is why the `click` tool's schema demands
|
|
25
|
+
"multiple commands targeting the SAME element" and why one click produced five ladder attempts,
|
|
26
|
+
one of them invalid JavaScript.
|
|
27
|
+
|
|
28
|
+
## Goals
|
|
29
|
+
|
|
30
|
+
- Prima is the executor for an expensive orchestrator: page data never enters that context on
|
|
31
|
+
the happy path, intent is never guessed, and every action carries proof of what changed.
|
|
32
|
+
- `do` acts on element handles it was given, not on locators it invented.
|
|
33
|
+
- Failures are failures. No code path may reach a different element than the one asked for.
|
|
34
|
+
- Attachment to an existing playwright-cli session works without hand-editing files.
|
|
35
|
+
|
|
36
|
+
## Non-Goals
|
|
37
|
+
|
|
38
|
+
- Replacing playwright-cli. It remains the fallback for direct driving and the tool prima
|
|
39
|
+
points back to when it cannot help.
|
|
40
|
+
- Reducing per-command latency, and improving `research` locator quality. Separate concerns.
|
|
41
|
+
- Changing Tester, Navigator, or Researcher behaviour outside the shared pieces named below.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. Perception ladder for `do`
|
|
46
|
+
|
|
47
|
+
Four tiers, tried in order. Each tier answers "what can I act on here?" at a different cost.
|
|
48
|
+
|
|
49
|
+
| Tier | Source | When |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| 1 | Research UI map | a stored map exists for this state hash and the state is well-visited |
|
|
52
|
+
| 2 | **ARIA snapshot with refs** | default |
|
|
53
|
+
| 3 | Compact HTML tree | a ref action failed, or the control is absent from the ARIA tree |
|
|
54
|
+
| 4 | Vision (`visualClick`) | click only, when the target is identifiable solely by appearance |
|
|
55
|
+
|
|
56
|
+
### Tier 1 — research map
|
|
57
|
+
|
|
58
|
+
Read through `getPreviousResearch(hash)` — a TTL-free disk read. Not `getCachedResearch`, whose
|
|
59
|
+
six-hour TTL carries session-scoped semantics that must not be stretched for cross-invocation
|
|
60
|
+
reuse. The trigger is the StateManager visit count for the current state hash, the same number
|
|
61
|
+
the envelope already prints as `visit #N`: use the map from the **third visit onward**, so a
|
|
62
|
+
state has to prove it is worth the map before prima prefers one. Below that, skip to tier 2.
|
|
63
|
+
The threshold is a single config field under `ai.agents.prima`, per the one-knob convention.
|
|
64
|
+
|
|
65
|
+
Research cost is real — 41s on the vision model in the field run — so tier 1 only pays off
|
|
66
|
+
amortized across repeat visits to one state. Prima does not run research on the caller's behalf
|
|
67
|
+
inside `do`; it consumes a map that already exists.
|
|
68
|
+
|
|
69
|
+
### Tier 2 — ARIA snapshot with refs (the default)
|
|
70
|
+
|
|
71
|
+
`page.locator('body').ariaSnapshot({ mode: 'ai' })` emits `[ref=eN]` handles. Playwright resolves
|
|
72
|
+
them natively through the `aria-ref=` selector engine, verified live against the running app:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
aria-ref=e1 → 1 match
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
No DOM mutation, no attribute stamping. The model is given the ref-bearing tree and names a ref;
|
|
79
|
+
resolution to a CodeceptJS command is §1.1. One ref becomes one command and the
|
|
80
|
+
multiple-locator fallback ladder collapses.
|
|
81
|
+
|
|
82
|
+
**Ref lifetime is one context injection.** Refs are snapshot-scoped and shift between calls —
|
|
83
|
+
in the field run the same sidebar button was `ref=e13` in one snapshot and `ref=e284` in a later
|
|
84
|
+
one on the same page. Prima re-snapshots whenever the `do` loop re-injects context on state-hash
|
|
85
|
+
change (the hook exists at `prima.ts:104`), and the prompt states that refs from an earlier
|
|
86
|
+
injection are dead.
|
|
87
|
+
|
|
88
|
+
**Ref shape.** Refs are frame-qualified: every ref this app emits is `f1e13`-shaped, not `e13`.
|
|
89
|
+
A validator that accepts only `e\d+` rejects every real ref and silently disables the whole tier,
|
|
90
|
+
so the accepted shape is `(f\d+)?e\d+`. Refs are never adapted or invented — a ref that does not
|
|
91
|
+
parse, or that resolves to nothing, is a failure with its own message.
|
|
92
|
+
|
|
93
|
+
### Tier 3 — compact HTML tree
|
|
94
|
+
|
|
95
|
+
The current `simplifiedHtml` path. Entered when a ref action fails or the target is not
|
|
96
|
+
represented in the accessibility tree.
|
|
97
|
+
|
|
98
|
+
### Descending the ladder — `context()`
|
|
99
|
+
|
|
100
|
+
The tiers above are only reachable if something can move between them mid-run. The `do` loop
|
|
101
|
+
re-injects context between iterations when the state hash changes, which leaves no way to
|
|
102
|
+
recover from a ref that died *within* an iteration — the model's only remaining moves would be
|
|
103
|
+
to guess a locator, which the prompt forbids, or to stop.
|
|
104
|
+
|
|
105
|
+
`do` therefore carries a `context` tool. First call returns the page as it is now with fresh
|
|
106
|
+
refs, which replace every ref the model was holding; a later call on the same page drops to
|
|
107
|
+
capped markup for elements the accessibility tree does not describe. It is the tier descent, not
|
|
108
|
+
a page dump.
|
|
109
|
+
|
|
110
|
+
This is deliberately **not** the `context` tool from `createAgentTools`. That one returns
|
|
111
|
+
`getInteractiveARIA()` — `compactAriaSnapshot` over `ActionResult.ariaSnapshot`, captured without
|
|
112
|
+
`mode: 'ai'`, so it carries **no refs** — plus a 6k-char HTML dump beside it. Offering it here
|
|
113
|
+
would hand the model a ref-less tree and push it straight back to guessed locators, undoing
|
|
114
|
+
tier 2.
|
|
115
|
+
|
|
116
|
+
### Tier 4 — vision
|
|
117
|
+
|
|
118
|
+
`visualClick` (`src/ai/tools.ts:791`), already implemented. Click only — a coordinate is not a
|
|
119
|
+
handle, so it cannot serve fills, selects, or assertions.
|
|
120
|
+
|
|
121
|
+
### 1.1 Acting on a ref — `clickRef` / `hoverRef` in `boat/prima/src/tools.ts`
|
|
122
|
+
|
|
123
|
+
CodeceptJS has no `aria-ref` locator, so a ref cannot be handed to `I.click()` directly. It is
|
|
124
|
+
resolved to a locator CodeceptJS does understand, using machinery that already exists:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
page.locator(`aria-ref=${ref}`)
|
|
128
|
+
→ WebElement.fromPlaywrightLocator(...) (web-element.ts:112)
|
|
129
|
+
→ I.click(<xpath>)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Which XPath, and the one that is not available.** `WebElement` declares two — `clickXPath`,
|
|
133
|
+
built attribute-first by `buildClickableXPath` (`utils/xpath.ts`), and `xpath`, the absolute
|
|
134
|
+
positional path. On this path only `clickXPath` exists: `fromPlaywrightLocator` builds through
|
|
135
|
+
`fromRawData`, which hardcodes `xpath: ''` (`web-element.ts:89`). Only `fromXPathMatch`, the
|
|
136
|
+
static-HTML path, populates the absolute form. So there is no positional fallback to reach for,
|
|
137
|
+
and that is the right outcome anyway — an absolute path is deduplication machinery, fragile the
|
|
138
|
+
moment the DOM shifts, and the ref resolution and the click happen on separate round-trips.
|
|
139
|
+
|
|
140
|
+
Verified live, ref → `clickXPath` → match count:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
e15 link "Checking the proxy…" //*[self::a and contains(.,"Checking the proxy and the firewall")] → 1
|
|
144
|
+
e19 button "Reload" //*[@id="reload-button"] → 1
|
|
145
|
+
e20 button "Details" //*[@id="details-button"] → 1
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Require a unique match.** Because there is no fallback, the resolved `clickXPath` is checked
|
|
149
|
+
to match exactly one element before it is used. Zero or many is a failure, reported as such —
|
|
150
|
+
never a click on an ambiguous match and never a retreat to guessed locators.
|
|
151
|
+
|
|
152
|
+
Ref acting lives in **separate tools in the boat** — `clickRef` and `hoverRef`, from a
|
|
153
|
+
`createRefTools` factory in `boat/prima/src/tools.ts`. `click` and `hover` in `src/ai/tools.ts`
|
|
154
|
+
are left byte-identical. A prima-only tool belongs to prima; core keeps only what every caller
|
|
155
|
+
uses, and lends the boat its result-shaping helpers (`successToolResult`, `failedToolResult`,
|
|
156
|
+
`commitNote`) rather than having them copied.
|
|
157
|
+
|
|
158
|
+
Adding an optional `ref` to the existing tools looks cheaper and is wrong. A tool's schema and
|
|
159
|
+
description are shared with every caller, and Tester never receives ref-bearing snapshots, so it
|
|
160
|
+
would be shown a parameter it can only fill by inventing one. Making `commands` optional to
|
|
161
|
+
accommodate the new field weakens the contract for Tester too. "Prima enables ref mode, Tester is
|
|
162
|
+
untouched" is not achievable through one definition: there is one definition, and Tester sees it.
|
|
163
|
+
|
|
164
|
+
Each ref tool takes a ref and nothing else, resolves it, executes one command, and reports that
|
|
165
|
+
command as `used:` — real CodeceptJS a generated test can keep. A ref that does not resolve is a
|
|
166
|
+
failure, not a cue to fall back to a guessed locator: it means the context is stale.
|
|
167
|
+
|
|
168
|
+
`hoverRef` exists for the same reason as `hover` — revealing hover-only controls is a
|
|
169
|
+
prerequisite for clicking them.
|
|
170
|
+
|
|
171
|
+
Prima keeps the locator tools alongside the ref tools: tier 3 works from markup that carries no
|
|
172
|
+
refs, and needs them. Tier 4 keeps the coordinate input.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 2. Heal is deleted
|
|
177
|
+
|
|
178
|
+
`heal()` (`prima.ts:428-460`) is removed, along with `--no-heal`, `PrimaOptions.heal`, the
|
|
179
|
+
`healed:` / `healNote` envelope fields, the `HealAttempt` type, and the
|
|
180
|
+
`### Healing attempts` section. The three call sites — `pw` (`:79`), `do` (`:135`), `go` (`:211`)
|
|
181
|
+
— go straight to `failureEnvelope`.
|
|
182
|
+
|
|
183
|
+
The failure envelope already does the right thing and becomes the only failure path: `ok: false`,
|
|
184
|
+
the exact error with its call log, and compact ARIA inline. Measured at 8.6s in the field run
|
|
185
|
+
against 34.8s for the heal path that got the answer wrong.
|
|
186
|
+
|
|
187
|
+
This removes the substitution defect at the root. No remaining code path can select an element
|
|
188
|
+
other than the one asked for, so no envelope can report success for an action the caller did not
|
|
189
|
+
request. Routine obstructions — an overlay covering a button, an element not yet visible — now
|
|
190
|
+
return the failure envelope, and the orchestrating model decides. That is the accepted cost: the
|
|
191
|
+
compact ARIA block is the one place page data deliberately enters the expensive context.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 3. Proof-carrying envelopes
|
|
196
|
+
|
|
197
|
+
Two defects with one cause. `renderOutcome` (`envelope.ts:76`) returns the **first** of
|
|
198
|
+
changes / answer / research / verdict, so `### Changes` is structurally impossible alongside
|
|
199
|
+
`### Verdict`, `### Answer`, or `### Research`. And `pageChanges` returns `ariaChanges ?? null`,
|
|
200
|
+
which renders nothing when the diff is empty or `previousState` is null.
|
|
201
|
+
|
|
202
|
+
- `### Changes` renders on every action envelope, showing `no change` explicitly when the tree is
|
|
203
|
+
identical. A caller can then tell "nothing happened" from "prima did not say".
|
|
204
|
+
- `renderOutcome` stops being mutually exclusive: `### Changes` renders alongside the command's
|
|
205
|
+
own outcome section.
|
|
206
|
+
- Prima captures an explicit before-snapshot rather than relying on whatever `stateManager`
|
|
207
|
+
holds at process start.
|
|
208
|
+
- New `### Steps` block for `do`: one line per instruction, each naming the ariaDiff entry that
|
|
209
|
+
proves it, or marked `unproven`.
|
|
210
|
+
|
|
211
|
+
### Refs must never reach the diff or hash pipeline
|
|
212
|
+
|
|
213
|
+
The ref-bearing snapshot is a **context artifact only**. Refs are stripped before hashing and
|
|
214
|
+
diffing. Storing the `mode: 'ai'` output in `ActionResult.ariaSnapshot` would poison every
|
|
215
|
+
`### Changes` block, because Playwright renumbers refs on each call. Measured on an identical
|
|
216
|
+
page whose refs merely shifted:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
ref churn only, page identical → diff count = 6
|
|
220
|
+
added: button "Cancel" [ref=e21], button "Save" [ref=e20], textbox "Name" [ref=e22]
|
|
221
|
+
removed: button "Cancel" [ref=e11], button "Save" [ref=e10], textbox "Name" [ref=e12]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Six phantom entries for a page that did not change. Capture ref-bearing and ref-free variants,
|
|
225
|
+
and feed only the ref-free one to `diffAriaSnapshots` and `getStateHash`.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 4. Executor prompt
|
|
230
|
+
|
|
231
|
+
`instructionSystemPrompt` (`prima.ts:493`) already says to stop when every instruction is done,
|
|
232
|
+
but nothing tracks per-instruction completion, so the loop runs until the model stops calling
|
|
233
|
+
tools. Two rules are added, stated as general principles rather than as counter-examples from
|
|
234
|
+
any debugging session:
|
|
235
|
+
|
|
236
|
+
- Act only on the listed instructions. An adjacent action that appears helpful is out of scope;
|
|
237
|
+
report it as an observation instead of performing it.
|
|
238
|
+
- For each instruction, cite the observed page change that proves it. An instruction that cannot
|
|
239
|
+
be tied to an observed change is reported unproven rather than claimed as done.
|
|
240
|
+
|
|
241
|
+
The prompt also states the ref contract: act on refs from the current context injection; refs
|
|
242
|
+
from an earlier injection are dead.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 5. Attachment and discovery
|
|
247
|
+
|
|
248
|
+
Three separate faults, all confirmed empirically.
|
|
249
|
+
|
|
250
|
+
**Connect with the daemon's own build when attached.** `connectDescriptor` (`prima.ts:374`)
|
|
251
|
+
tries prima's own playwright first and only falls back to `descriptor.playwrightLib` if connect
|
|
252
|
+
fails. Connect *succeeds* across builds, so the fallback is never reached — and then the tier-2
|
|
253
|
+
snapshot breaks:
|
|
254
|
+
|
|
255
|
+
| client lib | `connect()` | `ariaSnapshot({mode:'ai'})` |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| own playwright 1.62.1 | ok | `locator.ariaSnapshot: timeout: expected float, got undefined` |
|
|
258
|
+
| daemon playwright-core 1.62.0-alpha | ok | ok, 3376 bytes, `aria-ref=e1` → 1 match |
|
|
259
|
+
|
|
260
|
+
In attached mode, prefer `descriptor.playwrightLib` when present and fall back to our own. Carry
|
|
261
|
+
`playwrightLib` through the `--endpoint` path too, which currently hardcodes `''`.
|
|
262
|
+
|
|
263
|
+
**Stop keying discovery on `workspaceDir`.** No `@playwright/cli` release writes that field —
|
|
264
|
+
verified on 0.1.13 and 0.1.17. `parseDescriptor` (`pw-registry.ts:56`) requires it and
|
|
265
|
+
`selectDescriptor` filters on it, so every descriptor is dropped and discovery finds nothing no
|
|
266
|
+
matter what is installed. Resolution becomes: `--endpoint` → `--pw-session <title>` →
|
|
267
|
+
`PLAYWRIGHT_CLI_SESSION` → live descriptor titled `default` → the single live descriptor →
|
|
268
|
+
tool error listing candidate titles. Liveness-probe before selecting.
|
|
269
|
+
|
|
270
|
+
**Bump the playwright pin to `^1.62`.** `playwright@^1.60` could not `connect()` to a 1.61/1.62
|
|
271
|
+
browser server at all; 1.62.1 connects. The pin is the reason the original review concluded
|
|
272
|
+
attach was broken outright.
|
|
273
|
+
|
|
274
|
+
Consequences: `browser list` shows attachable sessions rather than reporting none while one is
|
|
275
|
+
live, and the no-browser error stops advising `playwright-cli open <url>` to someone who already
|
|
276
|
+
ran it.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## 6. `verify` honesty
|
|
281
|
+
|
|
282
|
+
`rules/navigator/verification-actions.md` offers nine assertions — `see`, `seeElement`,
|
|
283
|
+
`seeInField`, `seeInTitle`, `seeInSource` and their `dontSee*` counterparts — none of which
|
|
284
|
+
express enabled, disabled, checked, selected, or expanded. A claim about interactive state is
|
|
285
|
+
therefore unprovable by construction, and the field run reported a working feature as failing:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
verify "the Save button in the skill editor is now enabled ..."
|
|
289
|
+
→ passed: false, code: (empty), evidence: no assertion held on the current page
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The app was correct — `● unsaved` was displayed and the button was enabled.
|
|
293
|
+
|
|
294
|
+
- Add state assertions to the rule.
|
|
295
|
+
- `verify` distinguishes **assertion failed** from **could not express this assertion**. The
|
|
296
|
+
second is not a test failure and must not be reported as one.
|
|
297
|
+
- With §7's compaction fix, `verify` reads state from the same ref-bearing snapshot the tier-2
|
|
298
|
+
ladder produces.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 7. Prerequisite fix — ARIA compaction drops refs and state
|
|
303
|
+
|
|
304
|
+
`compactAriaSnapshot` keeps only the first bracket group on a line. Playwright emits state
|
|
305
|
+
attributes before `[ref=]`, so every stateful control loses its handle. Measured:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
button "Plain" [ref=e10] → ref KEPT
|
|
309
|
+
button "Active" [active] [ref=e13] → ref LOST
|
|
310
|
+
button "Disabled" [disabled] [ref=e14] → ref LOST
|
|
311
|
+
button "Pressed" [pressed] [ref=e15] → ref LOST
|
|
312
|
+
button "Expanded" [expanded] [ref=e16] → ref LOST
|
|
313
|
+
checkbox "Checked" [checked] [ref=e17] → ref LOST
|
|
314
|
+
button "Cursor" [ref=e18] [cursor=pointer] → ref KEPT
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The controls most worth acting on and asserting about are exactly the ones stripped of their
|
|
318
|
+
handle. **Keep every bracket group on a line.** No allow-list, no drop-list — parsing one group
|
|
319
|
+
and discarding the rest is the whole bug, and any rule about which groups survive re-creates it
|
|
320
|
+
the next time Playwright adds an attribute. This fix is a prerequisite for §1 tier 2 and enables
|
|
321
|
+
§6.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## 8. Envelope hygiene
|
|
326
|
+
|
|
327
|
+
- `used:` carries the winning line only — no concatenated ladder attempts, no comment lines. The
|
|
328
|
+
field run produced `I.click(".sidebar button:has-text("Workflows")")`, which is not valid
|
|
329
|
+
JavaScript, and a `// 1. Open dialog...` comment inside the code block.
|
|
330
|
+
- `click` and `fill` stop labelling themselves `do` in `command:`.
|
|
331
|
+
- `network.jsonl` is written and advertised only when requests were actually captured. It was
|
|
332
|
+
0 bytes across all 18 runs while being advertised in every envelope; an artifact line that
|
|
333
|
+
points at an empty file is worse than no line.
|
|
334
|
+
- Research annotation overlays are removed from the DOM after use. The browser is shared with
|
|
335
|
+
playwright-cli, and leftover `Legend` / `e8` / `e10` nodes appeared in its next snapshot.
|
|
336
|
+
- Commands stop requiring a URL when attached to a browser already on a page.
|
|
337
|
+
- A redirect that preserves origin and path counts as navigation success. The app's
|
|
338
|
+
`/` → `/?session=<uuid>&ws=1` redirect cost 3m00s and eight attempts under the old heal path,
|
|
339
|
+
and a hard tool failure against a prima-owned browser — in an envelope whose own inlined ARIA
|
|
340
|
+
proved the page had loaded.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Testing
|
|
345
|
+
|
|
346
|
+
- **Unit** — envelope rendering with `### Changes` always present, including the `no change`
|
|
347
|
+
form and the combination with `### Verdict` / `### Answer` / `### Research`; compaction
|
|
348
|
+
preserving every bracket group across the §7 combinations and any order of them; ref-free
|
|
349
|
+
diffing (the six-phantom case must yield zero); descriptor selection without `workspaceDir`,
|
|
350
|
+
including the ambiguous multi-session error.
|
|
351
|
+
- **Ref resolution** — a ref resolves to an attribute-based `clickXPath` matching exactly one
|
|
352
|
+
element, and still matching after unrelated siblings are added or removed; a ref that resolves
|
|
353
|
+
to zero or many fails rather than falling back to a guessed locator.
|
|
354
|
+
- **Integration** — `do` prompt behaviour through the existing `@copilotkit/aimock` harness per
|
|
355
|
+
`docs/contributing/ai-integration-tests.md`: instructions performed and nothing beyond them,
|
|
356
|
+
per-instruction proof citation, unproven reporting. Fictional fixture data only.
|
|
357
|
+
- **End-to-end** — against a local fixture: a ref named from the snapshot clicks the element it
|
|
358
|
+
names and no other; a failing `pw` returns `ok: false` with compact ARIA and never a
|
|
359
|
+
substituted action; attach to a live playwright-cli session with no descriptor editing; a
|
|
360
|
+
session-param redirect resolves as success.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Decisions Log
|
|
365
|
+
|
|
366
|
+
- Heal is deleted outright rather than constrained to same-target recovery. No flag, no opt-in.
|
|
367
|
+
- `do` perception is a four-tier ladder: research map, ARIA with refs, compact HTML, vision for
|
|
368
|
+
click.
|
|
369
|
+
- Refs come from Playwright's native `aria-ref=` engine. No eidx attribute stamping, so the
|
|
370
|
+
shared browser's DOM is not mutated.
|
|
371
|
+
- Ref acting lives in `boat/prima/src/tools.ts`, not in core; `click` and `hover` are untouched.
|
|
372
|
+
Extending a shared tool would show Tester a `ref` parameter it can only fill by inventing one,
|
|
373
|
+
and would weaken `commands` for every caller.
|
|
374
|
+
- A ref resolves through `WebElement` to an attribute-based `clickXPath`. The absolute positional
|
|
375
|
+
`xpath` is not a fallback — `fromRawData` never populates it on the live-locator path, and it
|
|
376
|
+
would be the fragile choice regardless.
|
|
377
|
+
- A resolved `clickXPath` must match exactly one element. A dead or ambiguous ref is a failure,
|
|
378
|
+
never a fallback to guessed locators.
|
|
379
|
+
- ARIA compaction keeps every bracket group. No allow-list of attributes to preserve.
|
|
380
|
+
- Refs are a context artifact only, stripped before hashing and diffing.
|
|
381
|
+
- Ref lifetime is one context injection; the loop re-snapshots on state-hash change.
|
|
382
|
+
- Tier 1 reads maps via `getPreviousResearch`; the trigger is StateManager visit count.
|
|
383
|
+
- Discovery matches on title plus liveness; `workspaceDir` is abandoned as a key.
|
|
384
|
+
- Attached mode connects with `descriptor.playwrightLib` first, because connect succeeds across
|
|
385
|
+
builds but `ariaSnapshot` does not.
|
|
386
|
+
- The playwright pin moves to `^1.62`.
|
|
387
|
+
- `verify` reports inexpressible assertions as inexpressible, never as failures, and does not
|
|
388
|
+
record them as verifications — a claim that could not be checked must not be remembered as
|
|
389
|
+
one that failed.
|
|
390
|
+
- `do` carries a prima-shaped `context` tool that descends the ladder — fresh refs first, capped
|
|
391
|
+
markup on a repeat call. The shared `context` from `createAgentTools` is not reused: it returns
|
|
392
|
+
a ref-less tree and an HTML dump.
|
|
393
|
+
- The baseline snapshot is captured lazily when a command needs a diff, not in `start()`, which
|
|
394
|
+
runs before a page is loaded.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Prima False Verdicts — No Reload, Vision-Confirmed Outcomes, Unconfirmed ≠ Failed
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-18
|
|
4
|
+
**Status:** Implemented
|
|
5
|
+
**Follows:** `2026-08-07-prima-fixes-design.md`
|
|
6
|
+
**Evidence:** field feedback from an orchestrator driving prima over eight calls
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
Prima returned verdicts that did not match what happened, in both directions.
|
|
11
|
+
|
|
12
|
+
- `prima do` reported `error: open: <instruction>` for clicks that had landed. The caller only
|
|
13
|
+
found out by screenshotting anyway — which is the cost the boat exists to remove.
|
|
14
|
+
- `prima check` returned `ok: false` for an environmental reason and said nothing about it.
|
|
15
|
+
- No command answered a visual question with a verdict. `check` and `verify` never read a
|
|
16
|
+
screenshot; `ask` read one but returned prose.
|
|
17
|
+
|
|
18
|
+
The skill tells callers to trust `### Result`. A false red trains them out of that, and then the
|
|
19
|
+
greens stop meaning anything either.
|
|
20
|
+
|
|
21
|
+
## Mechanisms found
|
|
22
|
+
|
|
23
|
+
1. **`check` reloaded the page before checking it.** `prima.check()` → `tester.test()` →
|
|
24
|
+
`runTestSession` → `explorer.visit(task.startUrl!)` (`tester.ts:192`, unconditional) →
|
|
25
|
+
`I.amOnPage()` (`explorer.ts:431`) → `page.goto()`, which reloads even on the same URL.
|
|
26
|
+
`task.startUrl` is the page the caller is already on. Any transient state — an open dialog, a
|
|
27
|
+
selected tab, an unsaved form — was destroyed by the command asked to inspect it. This was
|
|
28
|
+
guaranteed, not a race with a dev-server reload.
|
|
29
|
+
2. **`check` could not say why it failed.** `reportEnvelope` never sets `failure`, and
|
|
30
|
+
`envelope.steps` was built only from notes with `status === FAILED`. An abort produced
|
|
31
|
+
`ok: false` with an empty Steps section and no Failure section.
|
|
32
|
+
3. **`do`'s verdict was the model's bookkeeping, not the page.** An instruction the model never
|
|
33
|
+
passed to `completed()` was rendered as an error, so an envelope could show every step green
|
|
34
|
+
and `ok: false` at once. `settleLedger`'s `.catch(() => null)` made a provider error
|
|
35
|
+
indistinguishable from a model that would not report.
|
|
36
|
+
4. **Vision routing was per command, not per question.** `verify` produced DOM assertions only;
|
|
37
|
+
`check`'s verdict came from that same tool plus `settleExpectations`, which judged a text log.
|
|
38
|
+
The `inexpressible` branch told the model to "check it with `see()`" with no model in the loop
|
|
39
|
+
to act on the suggestion.
|
|
40
|
+
5. **`prima status` printed the page tree.** `saveStatus` stored the full compact ARIA under
|
|
41
|
+
`changes`, so the command whose job is to cite artifact paths dumped the tree inline instead.
|
|
42
|
+
|
|
43
|
+
## Changes
|
|
44
|
+
|
|
45
|
+
### 1. `check` starts where the caller is
|
|
46
|
+
|
|
47
|
+
`Tester.test(task, opts)` takes `startOnCurrentPage`, which skips the initial visit. Prima passes
|
|
48
|
+
it. Nothing else changes for the explore flow, where reload-to-start-url is intentional.
|
|
49
|
+
|
|
50
|
+
`reset` needs no guard: it already refuses when the current URL equals the start URL, which is
|
|
51
|
+
the case for a check that starts in place. Once a check has navigated away, resetting back is
|
|
52
|
+
the right behaviour anyway.
|
|
53
|
+
|
|
54
|
+
### 2. The screenshot is the proof, and a disagreement is a finding
|
|
55
|
+
|
|
56
|
+
`settleExpectations` is called from exactly one place, `prima.ts`, so it is prima's final judge
|
|
57
|
+
and can change without touching the explore flow. It now takes the final `ActionResult` and, when
|
|
58
|
+
that carries a screenshot and a vision model is configured, settles every outcome in one
|
|
59
|
+
structured call on the vision model with the image attached.
|
|
60
|
+
|
|
61
|
+
The screenshot is not one of two equal inputs. An outcome is satisfied when the page shows it to
|
|
62
|
+
somebody looking at it; the log only says what the run did. The prompt says so.
|
|
63
|
+
|
|
64
|
+
**Where the two disagree, the judge does not choose.** It reports `contradiction` and says what each
|
|
65
|
+
side shows. An assertion that matched an element nobody can see is a defect in the application, and
|
|
66
|
+
it is exactly the case both other verdicts destroy: `passed` hides it behind an assertion that
|
|
67
|
+
happens to match, `failed` mislabels a feature that half works. It comes back as its own status
|
|
68
|
+
with both sides quoted, it fails the command, and `### Artifacts` names the html, aria and screenshot
|
|
69
|
+
on disk so the caller can settle it on the page itself rather than on the judge's word.
|
|
70
|
+
|
|
71
|
+
**Absence in the picture is not a contradiction.** Review of the first pass raised this: a
|
|
72
|
+
screenshot is not proof that a thing is missing, only that the judge could not make it out. A
|
|
73
|
+
contradiction now requires the picture to show something *incompatible* — a list visibly empty, an error
|
|
74
|
+
where a result was expected, the old value still displayed. "I cannot find it" is `unverified`,
|
|
75
|
+
which does not fail the command.
|
|
76
|
+
|
|
77
|
+
The screenshot is the final page only. An outcome the run established earlier stays established
|
|
78
|
+
even when the page has moved past it, and the prompt says that is not a contradiction — otherwise every
|
|
79
|
+
"record deleted, then navigated away" scenario reports one.
|
|
80
|
+
|
|
81
|
+
### 3. `check`'s verdict is its outcomes
|
|
82
|
+
|
|
83
|
+
`ok` no longer comes from `tester.test()`'s success flag, which could contradict the outcomes
|
|
84
|
+
printed beside it. `ok: true` when no outcome failed and none was contradicted; each failure and
|
|
85
|
+
each contradiction names itself in `### Failure`. `unverified` is not a failure — it is a statement about
|
|
86
|
+
the run, matching what the help text already promised.
|
|
87
|
+
|
|
88
|
+
A run that could not complete is reported separately from an application failure: when the test
|
|
89
|
+
never finished or was skipped, the envelope says the run established nothing and cites the last
|
|
90
|
+
step recorded.
|
|
91
|
+
|
|
92
|
+
### 4. `do` distinguishes failed from unconfirmed
|
|
93
|
+
|
|
94
|
+
`ok` is a function of what ran, not of the paperwork. An action error or a `blocked()` fails the
|
|
95
|
+
command. An instruction the model never reported becomes a `??` row in `### Steps` — the actions
|
|
96
|
+
that ran are listed above it — and does not fail the command. An AI error while settling the
|
|
97
|
+
ledger is reported as its own step rather than attributed to the instruction.
|
|
98
|
+
|
|
99
|
+
The `<proof>` block gains one general line: how much of the page moved is not evidence of whether
|
|
100
|
+
something happened. A change confined to one region proves an instruction as well as one that
|
|
101
|
+
redraws everything.
|
|
102
|
+
|
|
103
|
+
### 5. `verify` reaches for vision when no assertion can express the claim
|
|
104
|
+
|
|
105
|
+
The `inexpressible` branch now judges the claim from a screenshot and reports the judgement,
|
|
106
|
+
instead of dead-ending on a suggestion nothing acts on. Because Tester's `verify` tool calls the
|
|
107
|
+
same `navigator.verifyState`, this covers `check` as well.
|
|
108
|
+
|
|
109
|
+
`Prima.visionEnabled()` also honours `Stats.visionDisabled`, so a session that lost vision
|
|
110
|
+
mid-run stops claiming to have it.
|
|
111
|
+
|
|
112
|
+
### 6. `status` cites artifacts instead of reprinting the page
|
|
113
|
+
|
|
114
|
+
The ARIA blob is gone from `status.json`. `status` returns the page block and the artifact paths,
|
|
115
|
+
which is its whole job.
|
|
116
|
+
|
|
117
|
+
## Decisions Log
|
|
118
|
+
|
|
119
|
+
- `check` starts on the current page. A command that inspects transient UI must not destroy it.
|
|
120
|
+
- Vision is not a fallback in `check`; it closes every run that has a vision model. The
|
|
121
|
+
screenshot is the proof — what a user can see — and the run log only says what was done.
|
|
122
|
+
- A disagreement between the picture and the run is reported as `contradiction`, never settled one
|
|
123
|
+
way. An assertion matching an element nobody can see is a defect, and both `passed` and `failed`
|
|
124
|
+
would bury it. A contradiction fails the command, and hands the caller the page files to judge on.
|
|
125
|
+
The word is `contradiction` rather than `conflict` because it names what happened, and rather than
|
|
126
|
+
`ambiguity` because that is what `unverified` already means.
|
|
127
|
+
- A contradiction needs the picture to show something incompatible. Not finding something is `unverified`;
|
|
128
|
+
absence of evidence is not evidence of absence, and treating it as one is how a false-verdict fix
|
|
129
|
+
becomes a false-verdict generator.
|
|
130
|
+
- Nothing probes the page to explain *why* something is invisible. A first attempt walked the DOM
|
|
131
|
+
comparing colours, sniffing screen-reader patterns and hit-testing every element; it was a pile of
|
|
132
|
+
heuristics guessing at an answer the contradiction already states. "The run says it is there and
|
|
133
|
+
the picture does not show it" is the finding, and the caller is better placed to say why.
|
|
134
|
+
- `settleExpectations` judges all outcomes, not only undecided ones, when it has a screenshot —
|
|
135
|
+
otherwise a DOM assertion the run already made could never be contradicted by the page.
|
|
136
|
+
- `unverified` is not a failure, in `check` outcomes and in `do` instructions alike. A statement
|
|
137
|
+
about the run is not a statement about the application.
|
|
138
|
+
- Bookkeeping is not evidence. `do`'s `ok` follows actions and blocks; an unreported instruction
|
|
139
|
+
is surfaced, never rendered as an error.
|
|
140
|
+
- A run that could not complete is reported as such, never as an application failure.
|
|
141
|
+
- `provider.getVisionModel()` is added for symmetry with `getModelForAgent`/`getAgenticModel`;
|
|
142
|
+
`processImage` returns free text and cannot carry a per-outcome verdict.
|
|
143
|
+
- A failed vision judgement falls back to the text model and flips `Stats.visionDisabled` — the
|
|
144
|
+
existing global for "vision is not usable this session" — which prima reads through
|
|
145
|
+
`visionEnabled()`.
|
|
146
|
+
- No line reports which evidence the judge had; that is plumbing. Only the degraded case is
|
|
147
|
+
stated, as a `### Warning`, because only that case is a fact the caller must act on.
|
|
148
|
+
- The verdict vocabulary lives in `prima <command> --help`. A marker the caller can see in an
|
|
149
|
+
envelope but cannot look up is not documented.
|
|
150
|
+
|
|
151
|
+
## Not done
|
|
152
|
+
|
|
153
|
+
- `explorer.beginTest` still calls `closeOtherTabs()`, so `check` closes other tabs of an
|
|
154
|
+
attached session. Guarding it means threading an option through `beginTest`, which every flow
|
|
155
|
+
shares.
|
|
156
|
+
- `do` gets no vision confirmation pass. Its `completed()` proof is the same kind of unverified
|
|
157
|
+
paperwork, but a per-instruction vision call is a different cost profile.
|
|
158
|
+
- The `prima` skill in `testomatio/skills` documents the old envelope vocabulary. It needs the
|
|
159
|
+
`??` row, the `CONTRADICTION` status, and the unconfirmed-is-not-failed rule.
|