Skip to content

feat: add proof worker — AI-powered browser testing - #2

Merged
rohitg00 merged 6 commits into
mainfrom
feat/proof-worker
Apr 22, 2026
Merged

feat: add proof worker — AI-powered browser testing#2
rohitg00 merged 6 commits into
mainfrom
feat/proof-worker

Conversation

@rohitg00

@rohitg00 rohitg00 commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Summary

proof is an iii worker that scans code changes and verifies them in a real Chromium browser using snapshot-driven accessibility testing.

Any agent connected to the engine (Claude Code, Codex) can drive the browser through iii functions — no API key needed. For CI/automated runs, proof drives Claude directly via the Anthropic API.

How it works

proof::scan       → git diff (unstaged, staged, branch, commit)
proof::coverage   → import graph → which files lack tests
proof::execute    → agent loop with 14 browser tools
proof::report     → results → iii State + Stream

The snapshot-driven approach: proof::browser::snapshot returns an ARIA accessibility tree with [ref=eN] markers. The agent reads refs, not CSS selectors — making tests resilient to UI changes.

What's included

  • 25 functions registered with iii (14 browser tools + 11 pipeline)
  • 8 HTTP endpoints for REST access
  • Snapshot-driven testing — ARIA tree with ref IDs, not fragile selectors
  • Cookie extraction from Chrome and Firefox for authenticated testing
  • Test coverage analysis — import graph walks to find untested files
  • CDP auto-discovery — connect to existing Chrome instances
  • Flow save/replay — save successful runs to iii State, replay without AI
  • Queue + DLQ — CI runs with auto-retry via iii Queue
  • Real-time progress — step updates via iii Streams
  • OTel tracing — every browser action traced
  • 1,506 lines across 8 TypeScript files

iii primitives used

Primitive Usage
Functions 25 registered
Triggers 8 HTTP endpoints
State Reports (proof:reports), saved flows (proof:flows)
Streams Real-time step progress
Queue + DLQ CI runs with retry
Logger OTel-traced actions

Quick start

iii --use-default-config              # Terminal 1
cd proof && npm install && npm run dev  # Terminal 2

Then in Claude Code: "test my changes at localhost:3000"

Tested

  • 22/22 E2E tests passing (all via iii.trigger())
  • 2 rounds of CodeRabbit review, all findings fixed
  • 2 rounds of code simplification
  • TypeScript strict mode, zero errors
  • Headed browser demo verified on macOS

Test plan

  • iii trigger --function-id='proof::scan' --payload='{"target":"unstaged"}'
  • iii trigger --function-id='proof::browser::launch' --payload='{"runId":"t1","headed":true}'
  • iii trigger --function-id='proof::browser::navigate' --payload='{"url":"https://example.com"}'
  • iii trigger --function-id='proof::browser::snapshot' --payload='{}'
  • iii trigger --function-id='proof::browser::click' --payload='{"ref":"e2"}'
  • iii trigger --function-id='proof::browser::close' --payload='{"runId":"t1"}'
  • curl localhost:3111/proof/flows returns []
  • curl localhost:3111/proof/history returns []

Summary by CodeRabbit

  • New Features

    • Introduced AI-powered browser testing worker that drives Chromium automation via intelligent agents.
    • Added browser interaction tools: navigate, click, type, select, key press, screenshot, and assertion logging.
    • Implemented test coverage analysis linking changed files to test coverage.
    • Added saved flow replay capability for rerunning test sequences.
    • Exposed HTTP endpoints for CI/API integration and browser session management.
    • Added cookie extraction and injection for authenticated testing scenarios.
  • Documentation

    • Added comprehensive README documenting architecture, quick-start, usage instructions, and API reference.

proof is an iii worker that scans code changes and verifies them in a
real Chromium browser using snapshot-driven accessibility testing.

25 registered functions:
- 14 browser tools (navigate, snapshot, click, type, screenshot,
  console logs, network requests, performance metrics, raw Playwright
  exec, assertions, CDP discovery, cookie injection)
- 11 pipeline functions (scan, coverage, execute, report, run,
  replay, flows, history, enqueue, cleanup)

8 HTTP endpoints for REST access.

Uses iii primitives throughout:
- All inter-function calls via iii.trigger()
- State for reports and saved flows
- Streams for real-time progress
- Queue + DLQ for CI runs with auto-retry
- Logger with OTel tracing

Default mode: Claude Code or Codex as the agent (no API key).
Automated mode: Anthropic API for headless CI (needs ANTHROPIC_API_KEY).

1,506 lines across 8 TypeScript files.
@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@rohitg00 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 7 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 23 minutes and 7 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1bc0ab90-d5cb-4af5-a3e0-0fde30c548cc

📥 Commits

Reviewing files that changed from the base of the PR and between fad4d00 and 9385e8b.

📒 Files selected for processing (7)
  • proof/README.md
  • proof/package.json
  • proof/src/agent.ts
  • proof/src/browser.ts
  • proof/src/context.ts
  • proof/src/cookies.ts
  • proof/src/worker.ts
📝 Walkthrough

Walkthrough

A new "proof" package is introduced as an AI-powered browser testing worker that integrates with an iii engine. It enables Claude agents to drive Chromium via snapshot-based element referencing (using ARIA trees and [ref=eN] markers), supporting interactive browser actions, inspection utilities, and pipeline orchestration through scan → coverage → execute → report workflows with HTTP endpoints and queue-based execution.

Changes

Cohort / File(s) Summary
Setup & Configuration
proof/README.md, proof/package.json, proof/tsconfig.json
New package documentation, npm metadata with dependencies (iii-sdk, playwright, simple-git), development scripts, TypeScript configuration targeting ES2022/ESNext with strict type-checking.
Type System
proof/src/types.ts
Foundational TypeScript definitions for step/run reporting (StepResult, RunReport), saved flows, scan results, browser sessions (BrowserSession encapsulating Playwright artifacts + state), reference tracking, console/network events, and run input parameters.
Prompt Engineering
proof/src/prompt.ts
Large instructional SYSTEM_PROMPT describing snapshot-first QA workflow, tool inventory, step markers, scope rules, and debugging guidance; buildUserPrompt() assembles user-facing prompt with optional instruction, base URL, changed files, commits, coverage details, and diff.
Tool Definitions
proof/src/tools.ts
Tool inventory (TOOLS array) covering browser navigation, snapshots, element actions (click/type/select/press), screenshots, assertions, console/network/performance inspection, and code execution; utilities for mapping tool names to function IDs and formatting for Anthropic API.
Agent & Orchestration
proof/src/agent.ts
Iterative Claude tool-using agent loop with progress streaming, step/assertion tracking, dynamic Anthropic SDK import, up to 50 iterations, embedded step-marker parsing (STEP_START, STEP_DONE, ASSERTION_*, RUN_COMPLETED), tool execution via callback, error handling, and comprehensive RunReport synthesis.
Browser Automation
proof/src/browser.ts
Playwright-based session management with per-runId browser/context/page provisioning, CDP discovery, ARIA snapshot generation with stable element references (e1, e2, …), action handlers (navigate, click, type, select, press), inspection utilities (screenshots, console logs, network requests, performance metrics, Playwright code execution), and lifecycle cleanup.
Context & Coverage Analysis
proof/src/context.ts
Git diff scanning for unstaged/staged/branch/commit targets with main-branch detection and commit history extraction; test coverage analysis mapping changed source files to importing test files with per-file and aggregate coverage reporting.
Cookie Extraction
proof/src/cookies.ts
Platform-aware cookie extraction for Chrome/Firefox with OS-specific database paths (darwin/linux), external sqlite3 query execution, normalization of cookie attributes (expires, sameSite), and injection into active browser session via Playwright API.
Worker & HTTP Integration
proof/src/worker.ts
iii worker exposing run orchestration (proof::run, proof::scan, proof::coverage, proof::execute, proof::report), flow replay (proof::replay), state/history queries (proof::flows, proof::history), lifecycle management (proof::cleanup), queue-based execution (proof::enqueue), HTTP endpoints for all top-level functions, and single active-run guard via activeRunId.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Worker as iii Worker
    participant Agent as Claude Agent
    participant BrowserAPI as Browser/Playwright
    participant State as State & Stream

    User->>Worker: POST /proof (run request)
    Worker->>Worker: proof::scan (analyze diffs)
    Worker->>Worker: proof::coverage (test coverage)
    Worker->>Worker: proof::execute
    Worker->>BrowserAPI: launch browser session
    Worker->>BrowserAPI: inject cookies (optional)
    
    Worker->>Agent: initialize with system prompt + user prompt
    loop Agent Loop (max 50 iterations)
        Agent->>Agent: call Claude API with tools
        alt Tool Use Response
            Agent->>Worker: trigger (function_id, payload)
            Worker->>BrowserAPI: execute browser action<br/>(navigate, click, type, etc.)
            BrowserAPI->>BrowserAPI: perform action on page
            BrowserAPI-->>BrowserAPI: build ARIA snapshot
            BrowserAPI-->>Worker: return result + snapshot
            Worker-->>Agent: return tool_result
        else End Turn
            Agent-->>Worker: stop_reason = end_turn
            Worker->>Worker: exit loop
        end
        Worker->>State: push step progress (best-effort)
    end
    
    Worker->>BrowserAPI: close browser
    Worker->>Worker: proof::report (store results)
    Worker->>State: save flow (if pass)
    Worker->>State: push stream update
    Worker-->>User: return RunReport
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit built a testing warren, where Claude clicks and types with care,
Through ARIA snapshots, tool zaps, the Chromium runs everywhere,
With agent loops and coverage proofs, each ref marked true and keen,
From proof to flow, we run the show—the finest tests you've seen! 🖱️✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add proof worker — AI-powered browser testing' clearly and concisely summarizes the main change: introducing a new proof worker with AI-powered browser testing capabilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/proof-worker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (6)
proof/README.md (2)

113-127: Add language identifier to fenced code block.

The static analysis tool flagged this ASCII flow diagram as missing a language specification. Use text or plaintext to satisfy linters while preserving the diagram formatting.

📝 Suggested fix
-```
+```text
 proof::scan          git diff → changed files, commits
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/README.md` around lines 113 - 127, The fenced ASCII diagram block
containing the flow lines starting with "proof::scan ... proof::report" is
missing a language identifier; update that code fence to include a plaintext
specifier (e.g., ```text or ```plaintext) so linters accept the block while
preserving the diagram formatting.

230-249: Add language identifier to architecture diagram code block.

Same as above — add text or plaintext language identifier.

📝 Suggested fix
-```
+```text
 ┌──────────────────────────────────────────┐
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/README.md` around lines 230 - 249, The README's ASCII architecture
diagram code block (the triple-backtick block containing the "iii Engine"
diagram) lacks a language identifier; update the opening fence from ``` to
```text (or ```plaintext) so the diagram is rendered as plain text—locate the
block that starts with the ASCII art including "iii Engine" and change its
opening backticks accordingly.
proof/src/context.ts (2)

169-180: resolveImportPath returns the unresolved path as fallback.

When no file matches, the function returns the normalized path anyway (line 179). This could lead to false coverage associations if the test imports a file that doesn't exist or is generated at build time.

♻️ Consider returning null for unresolved imports
   for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"]) {
     const full = path.join(root, resolved + ext);
     try {
       if (fs.statSync(full).isFile()) return resolved + ext;
     } catch { /* not found */ }
   }
-  return resolved;
+  return null;

The caller at line 104 already handles null returns appropriately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/src/context.ts` around lines 169 - 180, resolveImportPath currently
returns the normalized path as a fallback even when no matching file exists,
which can create false associations; change the function to return null when no
file was found (instead of returning resolved at the end), keeping the existing
file-check loop and error handling in resolveImportPath so callers (e.g., the
caller at line 104) receive null for unresolved imports and can handle them
appropriately.

129-148: Async function uses synchronous filesystem operations.

findTestFiles is marked async but uses fs.readdirSync. Similarly, extractImports (line 150) uses fs.readFileSync. While functionally correct, this blocks the event loop during file scanning. For large repositories, consider using async variants (fs.promises.readdir, fs.promises.readFile).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/src/context.ts` around lines 129 - 148, The functions findTestFiles and
extractImports use synchronous fs calls; change them to non-blocking async calls
by replacing fs.readdirSync with await fs.promises.readdir(fullDir, {
withFileTypes: true }) in findTestFiles (keep the recursive await findTestFiles
logic) and replace fs.readFileSync in extractImports with await
fs.promises.readFile(filePath, "utf8"); ensure you catch and handle errors (use
catch(err) to propagate or return results as before) and preserve checks like {
withFileTypes: true } and TEST_PATTERN/SKIP_DIRS behavior so the public APIs
(findTestFiles, extractImports) remain async and non-blocking.
proof/src/agent.ts (1)

38-43: Consider typing the messages array more strictly.

Using any[] for the messages array loses type safety. The Anthropic SDK provides message types that could be used here.

♻️ Suggested improvement
+import type { MessageParam } from "@anthropic-ai/sdk/resources/messages";
+
-  const messages: any[] = [
+  const messages: MessageParam[] = [
     {
       role: "user",
       content: buildUserPrompt(diff, files, baseUrl, instruction, commits, coverage),
     },
   ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/src/agent.ts` around lines 38 - 43, The messages array is typed as
any[] which loses type safety; change it to the Anthropic SDK message type
(e.g., use the exported message interface/type from the SDK such as
AnthropicMessage or ChatCompletionRequestMessage depending on your SDK version)
and update the array declaration for messages to that type, ensuring the object
created with buildUserPrompt conforms to that message type; add the appropriate
import of the message type at the top of the file and adjust buildUserPrompt's
return type if needed so the element matches the SDK message interface.
proof/src/worker.ts (1)

175-181: Edge case: empty slug base when title has no alphanumeric characters.

If report.title contains only non-alphanumeric characters (e.g., "..."), the base becomes an empty string and the slug is just the timestamp. Consider adding a fallback.

♻️ Suggested improvement
     const base = report.title
       .toLowerCase()
       .replace(/[^a-z0-9]+/g, "-")
       .replace(/^-|-$/g, "")
-      .slice(0, 50);
+      .slice(0, 50) || "flow";
     const slug = `${base}-${Date.now().toString(36)}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/src/worker.ts` around lines 175 - 181, When building the slug in the
pass-report branch (where report.status, report.steps, base, slug are used),
handle the edge case where base becomes empty by providing a fallback token
(e.g., "report" or "result" plus a short unique suffix) before composing slug;
update the slug construction to use that fallback when base === "" so the final
slug is not just the timestamp and remains readable/consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@proof/README.md`:
- Around line 167-202: The section headers miscount the functions: update
"Browser Tools (12)" to "Browser Tools (14)" (table lists
proof::browser::launch, close, navigate, snapshot, click, type, select, press,
screenshot, console_logs, network, performance, exec, assert) and update
"Pipeline (10)" to "Pipeline (12)" (table lists proof::scan, coverage, execute,
report, run, replay, flows, history, enqueue, cleanup, cookies::inject,
cdp::discover); also update any overall total function count references
elsewhere in the README to reflect the correct combined total (26) so all counts
stay consistent.

In `@proof/src/agent.ts`:
- Around line 45-113: The agent loop can silently exit when it reaches
MAX_ITERATIONS; after the for-loop that iterates up to MAX_ITERATIONS, detect if
iteration completed due to the limit (i.e., never broke out early) and then set
runStatus = "error" (or another sentinel) and log or record a warning (using
existing logging or pushStepProgress/runId) so the caller can surface the stuck
state; update any final pushStepProgress call to flush this status and/or append
a message to messages so the run is clearly marked as failed due to iteration
limit.

In `@proof/src/browser.ts`:
- Around line 237-250: handlePlaywrightExec currently runs the dynamically
constructed AsyncFunction (created via AsyncFunction and invoked as fn(page,
context, browser, ref)) with no timeout, so a hung/infinite script will block
the worker; wrap the invocation in a Promise.race between the fn(...) call and a
reject-after-timeout promise (use a configurable constant like EXEC_TIMEOUT_MS
or default to an appropriate value) so the race rejects when the timeout
elapses, and propagate that rejection; ensure the timeout is created and cleared
properly around the call to fn to avoid resource leaks and reference the
existing symbols handlePlaywrightExec, AsyncFunction, fn, and ref when locating
where to add the timeout logic.

In `@proof/src/context.ts`:
- Around line 36-43: When handling the "commit" case, guard against the
initial-commit edge where `${hash}^` has no parent; detect whether the parent
exists (e.g., call git.revparse([`${hash}^`]) or attempt a lightweight probe in
a try/catch) and if the parent does not exist, use a fallback pattern: compute
the diff against the empty tree or just diff the commit itself (use git.diff
with a single hash or diff against the empty tree), build files from that result
(still apply slice(0, MAX_FILES)), and set commits to the single commit from
git.log({ to: hash, maxCount: 1 }) or the log result you already have; update
uses of git.diff, git.diffSummary, and git.log in the "commit" branch to branch
on whether the parent exists and avoid referencing `${hash}^` when it does not.

In `@proof/src/cookies.ts`:
- Around line 129-134: The Firefox cookie extraction constructs an SQL query by
directly interpolating the domain into the execFileAsync call (the SELECT from
moz_cookies in the try block), which mirrors the injection risk in the Chrome
path; fix it by applying the same sanitation/validation used for Chrome: either
use a safe escape function on the domain value before interpolation or switch to
a parameterized query approach so the domain isn't injected raw into the SQL
string, and ensure the revised logic is applied to the execFileAsync call
constructing the moz_cookies SELECT statement.
- Around line 64-69: The current execFileAsync call builds a SQL string using
domain interpolation (see execFileAsync invocation and cookieDbPath) which risks
SQL injection; replace this by using a proper SQLite binding with parameterized
queries (e.g., better-sqlite3 or sqlite3) and bind the pattern `'%'+domain`
instead of interpolating, or strictly validate/whitelist the domain input before
usage; also add a short comment near cookieDbPath/execFileAsync noting that
Chrome cookie values are encrypted on macOS/Windows and may be unusable so
callers should expect empty values or document platform limitations.

In `@proof/src/worker.ts`:
- Around line 39-51: The run lock acquired by acquireRun(runId) in the
iii.registerFunction handler ("proof::browser::launch") isn't released if
launchBrowser throws; wrap the launchBrowser call in a try/catch and on error
call the corresponding release function (e.g., releaseRun(runId) or clear the
activeRunId) then rethrow the error so the lock is freed for subsequent runs; if
no releaseRun exists, add a function to clear activeRunId and call it in the
catch for the launchBrowser call.
- Around line 270-276: The current loop builds function IDs by stripping
"browser_" from action.tool before calling iii.trigger, which can produce
invalid IDs for non-browser actions; update the code in the flow processing loop
(the block iterating flow.actions and calling iii.trigger) to use the existing
mapping utility toolNameToFunctionId to derive the function_id from action.tool
(and handle undefined/missing mappings by logging an error or marking the result
as "fail" instead of calling iii.trigger), so iii.trigger receives a correct
function_id for every tool and results is updated appropriately.

---

Nitpick comments:
In `@proof/README.md`:
- Around line 113-127: The fenced ASCII diagram block containing the flow lines
starting with "proof::scan ... proof::report" is missing a language identifier;
update that code fence to include a plaintext specifier (e.g., ```text or
```plaintext) so linters accept the block while preserving the diagram
formatting.
- Around line 230-249: The README's ASCII architecture diagram code block (the
triple-backtick block containing the "iii Engine" diagram) lacks a language
identifier; update the opening fence from ``` to ```text (or ```plaintext) so
the diagram is rendered as plain text—locate the block that starts with the
ASCII art including "iii Engine" and change its opening backticks accordingly.

In `@proof/src/agent.ts`:
- Around line 38-43: The messages array is typed as any[] which loses type
safety; change it to the Anthropic SDK message type (e.g., use the exported
message interface/type from the SDK such as AnthropicMessage or
ChatCompletionRequestMessage depending on your SDK version) and update the array
declaration for messages to that type, ensuring the object created with
buildUserPrompt conforms to that message type; add the appropriate import of the
message type at the top of the file and adjust buildUserPrompt's return type if
needed so the element matches the SDK message interface.

In `@proof/src/context.ts`:
- Around line 169-180: resolveImportPath currently returns the normalized path
as a fallback even when no matching file exists, which can create false
associations; change the function to return null when no file was found (instead
of returning resolved at the end), keeping the existing file-check loop and
error handling in resolveImportPath so callers (e.g., the caller at line 104)
receive null for unresolved imports and can handle them appropriately.
- Around line 129-148: The functions findTestFiles and extractImports use
synchronous fs calls; change them to non-blocking async calls by replacing
fs.readdirSync with await fs.promises.readdir(fullDir, { withFileTypes: true })
in findTestFiles (keep the recursive await findTestFiles logic) and replace
fs.readFileSync in extractImports with await fs.promises.readFile(filePath,
"utf8"); ensure you catch and handle errors (use catch(err) to propagate or
return results as before) and preserve checks like { withFileTypes: true } and
TEST_PATTERN/SKIP_DIRS behavior so the public APIs (findTestFiles,
extractImports) remain async and non-blocking.

In `@proof/src/worker.ts`:
- Around line 175-181: When building the slug in the pass-report branch (where
report.status, report.steps, base, slug are used), handle the edge case where
base becomes empty by providing a fallback token (e.g., "report" or "result"
plus a short unique suffix) before composing slug; update the slug construction
to use that fallback when base === "" so the final slug is not just the
timestamp and remains readable/consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a8a06b6-9aeb-4bb2-9112-c98e1023e2fa

📥 Commits

Reviewing files that changed from the base of the PR and between a89c809 and fad4d00.

📒 Files selected for processing (11)
  • proof/README.md
  • proof/package.json
  • proof/src/agent.ts
  • proof/src/browser.ts
  • proof/src/context.ts
  • proof/src/cookies.ts
  • proof/src/prompt.ts
  • proof/src/tools.ts
  • proof/src/types.ts
  • proof/src/worker.ts
  • proof/tsconfig.json

Comment thread proof/README.md Outdated
Comment on lines +167 to +202
### Browser Tools (12)

| Function | Description |
|----------|-------------|
| `proof::browser::launch` | Launch Chromium (headed or headless, CDP optional) |
| `proof::browser::close` | Close browser session |
| `proof::browser::navigate` | Navigate to URL, return snapshot |
| `proof::browser::snapshot` | ARIA accessibility tree with `[ref=eN]` markers |
| `proof::browser::click` | Click element by ref |
| `proof::browser::type` | Type text into input by ref |
| `proof::browser::select` | Select dropdown option by ref |
| `proof::browser::press` | Press keyboard key on element |
| `proof::browser::screenshot` | Capture page as base64 PNG |
| `proof::browser::console_logs` | Read browser console messages |
| `proof::browser::network` | Read network request log |
| `proof::browser::performance` | Core Web Vitals (FCP, TTFB, CLS) |
| `proof::browser::exec` | Execute raw Playwright code |
| `proof::browser::assert` | Record a pass/fail assertion |

### Pipeline (10)

| Function | Description |
|----------|-------------|
| `proof::scan` | Git diff scanning (4 target modes) |
| `proof::coverage` | Import graph analysis → test coverage |
| `proof::execute` | Agent loop with Claude API |
| `proof::report` | Results → iii State + Stream |
| `proof::run` | Full pipeline orchestration |
| `proof::replay` | Replay a saved flow without AI |
| `proof::flows` | List saved flows |
| `proof::history` | Run history with trends |
| `proof::enqueue` | Queue-based run with retries + DLQ |
| `proof::cleanup` | Close all browser sessions |
| `proof::cookies::inject` | Extract local browser cookies |
| `proof::cdp::discover` | Find running Chrome CDP endpoint |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Function count headers don't match table contents.

The section header says "Browser Tools (12)" but the table lists 14 functions. Similarly, "Pipeline (10)" but the table lists 12 functions. Consider updating the headers to match the actual counts.

📝 Suggested fix
-### Browser Tools (12)
+### Browser Tools (14)
-### Pipeline (10)
+### Pipeline (12)

Also update line 19 and 239-240 to reflect the actual total (26 functions if all are counted).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proof/README.md` around lines 167 - 202, The section headers miscount the
functions: update "Browser Tools (12)" to "Browser Tools (14)" (table lists
proof::browser::launch, close, navigate, snapshot, click, type, select, press,
screenshot, console_logs, network, performance, exec, assert) and update
"Pipeline (10)" to "Pipeline (12)" (table lists proof::scan, coverage, execute,
report, run, replay, flows, history, enqueue, cleanup, cookies::inject,
cdp::discover); also update any overall total function count references
elsewhere in the README to reflect the correct combined total (26) so all counts
stay consistent.

Comment thread proof/src/agent.ts
Comment thread proof/src/browser.ts
Comment thread proof/src/context.ts
Comment thread proof/src/cookies.ts
Comment thread proof/src/cookies.ts
Comment thread proof/src/worker.ts Outdated
Comment thread proof/src/worker.ts
@rohitg00

Copy link
Copy Markdown
Contributor Author

Heads up — main is being bumped to iii-sdk 0.11.3 in #33. proof/package.json is pinned to ^0.11.0-next.9; bump to 0.11.3 so the worker depends on a stable release.

Node SDK 0.11.x changes: typed IIIInvocationError for rejections (with .code), ApiResponse now honors Content-Type. Existing catch(e) paths keep working.

Release notes: https://github.com/iii-hq/iii/releases/tag/iii/v0.11.3

…, count drift

- worker.ts: release run lock if launchBrowser throws; replace hand-rolled
  action.tool.replace('browser_','') with toolNameToFunctionId so every
  action (including browser_assert) routes to the correct function id.
  Also migrate all 26 registerFunction({ id: '...' }, ...) sites to the
  two-arg SDK signature required by iii-sdk 0.11.3.
- browser.ts: race handlePlaywrightExec against a 30s timeout so runaway
  user code can't wedge the worker event loop.
- agent.ts: surface MAX_ITERATIONS without end_turn as runStatus='error'
  and a console.warn instead of exiting silently.
- context.ts: fall back to the empty-tree hash when diffing the initial
  commit (no parent) so 'target=commit' works on first-commit repos.
- cookies.ts: validate hostname against [A-Za-z0-9.-] allow-list before
  interpolating into the sqlite3 LIKE clause. Document that Chrome/Firefox
  cookies on macOS and Windows are keychain-encrypted and return empty.
- README: Browser Tools (12)→(14), Pipeline (10)→(12).
@rohitg00
rohitg00 merged commit d04bfdf into main Apr 22, 2026
5 checks passed
@rohitg00
rohitg00 deleted the feat/proof-worker branch April 22, 2026 23:19
@coderabbitai coderabbitai Bot mentioned this pull request Apr 23, 2026
andersonleal added a commit that referenced this pull request Jul 24, 2026
rctest5 attempt 4 exposed the registration-vs-completion race: a
rejected sibling (the MOT-4210 divergent-spec check, working as
designed) forced a re-registration round, the watched writer sessions
finished inside that window, and the re-armed edge-triggered
turn-completed join predecessors starved forever — finalizer never
spawned, report never written, orchestrator parked.

A completion BARRIER is only correct level-triggered: registering a
join predecessor on a turn-completed binding whose filtered session is
ALREADY terminally completed (durable status completed, no armed wake)
now delivers a catch-up fire shaped like the real completion event,
stamped __late_subscription_replay. Joins only — their per-key
accumulator makes a rare double-delivery idempotent. Fail-open: a
status lookup failure keeps edge semantics. The registration response
carries a note naming the replay.

E2E-009 late-join-predecessor-replay gates it end to end: a worker
session completes (proved past via a call-mode completion witness —
recorder call #1), then a probe-steered turn registers the late join;
the joined downstream's recorder call #2 exists only through the
replay. Gate check: disabling the replay times the scenario out.
Runner: ProbeAction gains after_target_calls (an untracked session's
only observable milestone is a controlled-function call).

Verified live: rctest5 attempt 5 completed fully green — report row
self-verifying pass, totals == GROUP BY (5/5/5), 15/15 events, cited
trigger-spawned reactor, zero leaked subscriptions, no polling.
andersonleal added a commit that referenced this pull request Jul 24, 2026
rctest5 attempt 4 exposed the registration-vs-completion race: a
rejected sibling (the MOT-4210 divergent-spec check, working as
designed) forced a re-registration round, the watched writer sessions
finished inside that window, and the re-armed edge-triggered
turn-completed join predecessors starved forever — finalizer never
spawned, report never written, orchestrator parked.

A completion BARRIER is only correct level-triggered: registering a
join predecessor on a turn-completed binding whose filtered session is
ALREADY terminally completed (durable status completed, no armed wake)
now delivers a catch-up fire shaped like the real completion event,
stamped __late_subscription_replay. Joins only — their per-key
accumulator makes a rare double-delivery idempotent. Fail-open: a
status lookup failure keeps edge semantics. The registration response
carries a note naming the replay.

E2E-009 late-join-predecessor-replay gates it end to end: a worker
session completes (proved past via a call-mode completion witness —
recorder call #1), then a probe-steered turn registers the late join;
the joined downstream's recorder call #2 exists only through the
replay. Gate check: disabling the replay times the scenario out.
Runner: ProbeAction gains after_target_calls (an untracked session's
only observable milestone is a controlled-function call).

Verified live: rctest5 attempt 5 completed fully green — report row
self-verifying pass, totals == GROUP BY (5/5/5), 15/15 events, cited
trigger-spawned reactor, zero leaked subscriptions, no polling.
andersonleal added a commit that referenced this pull request Jul 24, 2026
rctest5 attempt 4 exposed the registration-vs-completion race: a
rejected sibling (the MOT-4210 divergent-spec check, working as
designed) forced a re-registration round, the watched writer sessions
finished inside that window, and the re-armed edge-triggered
turn-completed join predecessors starved forever — finalizer never
spawned, report never written, orchestrator parked.

A completion BARRIER is only correct level-triggered: registering a
join predecessor on a turn-completed binding whose filtered session is
ALREADY terminally completed (durable status completed, no armed wake)
now delivers a catch-up fire shaped like the real completion event,
stamped __late_subscription_replay. Joins only — their per-key
accumulator makes a rare double-delivery idempotent. Fail-open: a
status lookup failure keeps edge semantics. The registration response
carries a note naming the replay.

E2E-009 late-join-predecessor-replay gates it end to end: a worker
session completes (proved past via a call-mode completion witness —
recorder call #1), then a probe-steered turn registers the late join;
the joined downstream's recorder call #2 exists only through the
replay. Gate check: disabling the replay times the scenario out.
Runner: ProbeAction gains after_target_calls (an untracked session's
only observable milestone is a controlled-function call).

Verified live: rctest5 attempt 5 completed fully green — report row
self-verifying pass, totals == GROUP BY (5/5/5), 15/15 events, cited
trigger-spawned reactor, zero leaked subscriptions, no polling.
andersonleal added a commit that referenced this pull request Jul 24, 2026
rctest5 attempt 4 exposed the registration-vs-completion race: a
rejected sibling (the MOT-4210 divergent-spec check, working as
designed) forced a re-registration round, the watched writer sessions
finished inside that window, and the re-armed edge-triggered
turn-completed join predecessors starved forever — finalizer never
spawned, report never written, orchestrator parked.

A completion BARRIER is only correct level-triggered: registering a
join predecessor on a turn-completed binding whose filtered session is
ALREADY terminally completed (durable status completed, no armed wake)
now delivers a catch-up fire shaped like the real completion event,
stamped __late_subscription_replay. Joins only — their per-key
accumulator makes a rare double-delivery idempotent. Fail-open: a
status lookup failure keeps edge semantics. The registration response
carries a note naming the replay.

E2E-009 late-join-predecessor-replay gates it end to end: a worker
session completes (proved past via a call-mode completion witness —
recorder call #1), then a probe-steered turn registers the late join;
the joined downstream's recorder call #2 exists only through the
replay. Gate check: disabling the replay times the scenario out.
Runner: ProbeAction gains after_target_calls (an untracked session's
only observable milestone is a controlled-function call).

Verified live: rctest5 attempt 5 completed fully green — report row
self-verifying pass, totals == GROUP BY (5/5/5), 15/15 events, cited
trigger-spawned reactor, zero leaked subscriptions, no polling.
andersonleal added a commit that referenced this pull request Jul 24, 2026
… subscriptions (#586)

* (MOT-4217) fix(harness): let reaction sessions unregister their run's subscriptions

rctest5-K7mQ ended deadlocked: the repair reactor hit `subscription
belongs to a different session` trying to clean up the run, because
unregistration is owner-session-scoped and the owner — the orchestrator
— was parked waiting on a report notification its children could no
longer satisfy. Three armed subscriptions leaked.

Reaction spawns now record child → registrant lineage in the ephemeral
subscription registry, and the unregister ownership check accepts any
session whose lineage chain reaches the owner (transitively, hop-bounded
against re-targeted-session cycles). Lineage entries are purged on
session::deleted along with the session's subscriptions.

* (MOT-4217) test(harness): E2E-008 gates lineage unregister; serve-time captures; armed-wake advisory

Three pieces from the rctest5 live-run iteration:

* Router serve-time captures: a generation can capture a runtime value
  (regex over its matched request) and later frames echo it via
  [[cap:name]] — the only way a static fixture can call
  engine::unregister_trigger with a runtime-generated sub_… id.
  Validation requires declared-before-referenced and a capture group.

* E2E-008 reaction-unregisters-run: a STANDING binding's reaction, pinned
  to a separate session, unregisters the registrant's subscription. The
  gate is gen4's matcher demanding the unregister function_result with
  is_error:false AND removed:true (once:false is load-bearing — a
  one-shot binding retires itself first and the check never runs). The
  await phase now also drains the script after the awaited target call,
  so collection can't race an untracked session's turn tail. Gate check:
  reverting the ownership check to pre-lineage semantics times the
  scenario out.

* armed_wake_advisory: a one-shot state-key wake registration now warns
  that nothing fires it automatically and the session sleeps forever
  unless a registered task explicitly sets that scope/key — the exact
  wiring gap that left the rctest5 orchestrator parked with its cleanup
  pending after every row landed correctly.

* (MOT-4217) feat(harness): level-triggered join predecessors + E2E-009

rctest5 attempt 4 exposed the registration-vs-completion race: a
rejected sibling (the MOT-4210 divergent-spec check, working as
designed) forced a re-registration round, the watched writer sessions
finished inside that window, and the re-armed edge-triggered
turn-completed join predecessors starved forever — finalizer never
spawned, report never written, orchestrator parked.

A completion BARRIER is only correct level-triggered: registering a
join predecessor on a turn-completed binding whose filtered session is
ALREADY terminally completed (durable status completed, no armed wake)
now delivers a catch-up fire shaped like the real completion event,
stamped __late_subscription_replay. Joins only — their per-key
accumulator makes a rare double-delivery idempotent. Fail-open: a
status lookup failure keeps edge semantics. The registration response
carries a note naming the replay.

E2E-009 late-join-predecessor-replay gates it end to end: a worker
session completes (proved past via a call-mode completion witness —
recorder call #1), then a probe-steered turn registers the late join;
the joined downstream's recorder call #2 exists only through the
replay. Gate check: disabling the replay times the scenario out.
Runner: ProbeAction gains after_target_calls (an untracked session's
only observable milestone is a controlled-function call).

Verified live: rctest5 attempt 5 completed fully green — report row
self-verifying pass, totals == GROUP BY (5/5/5), 15/15 events, cited
trigger-spawned reactor, zero leaked subscriptions, no polling.

* style(harness): rustfmt across the stacked e2e additions

* review(harness): validate probe-action call gates like await_target_calls

Same authoring-mistake class CodeRabbit flagged on the await knob: a
zero after_target_calls fails at runtime and a positive one without a
controlled function waits until the deadline burns. Reject both in
ScenarioFixture::validate.
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.

1 participant