Public agent docs

QA Agent API

Raw markdown

QA Agent API

The agent integration uses Supabase Edge Functions as a gateway. Agents never

receive a user's Supabase session token. The agent requests an activation URL,

the user signs in at the app and activates the claim, then the agent exchanges

its one-time request secret for a scoped qa_agent_... token.

Public app URL: https://qa.felipeotarola.com

Public agent docs: https://qa.felipeotarola.com/qa-agent-api.md

Index

Start here:

- [Flow](#flow) - access approval and token exchange overview.

- [Recommended Agent Loop](#recommended-agent-loop) - the full project-first

loop agents should follow for real QA work.

Access:

- [Request Access](#request-access) - create an activation claim.

- [Exchange Approval](#exchange-approval) - exchange an approved request for a

scoped qa_agent_... token.

Projects and workspace memory:

- [List Projects](#list-projects)

- [Create Project](#create-project)

- [Project Brief](#project-brief) - one-call project intelligence for Sladdis

before planning a run.

- [QA Workspace](#qa-workspace) - durable project context, safe test data,

and retest queue.

- [Read QA Workspace](#read-qa-workspace)

- [Upsert Project Context](#upsert-project-context)

- [Create Test Data And Retest Items](#create-test-data-and-retest-items)

- [Create Agent Field Notes](#create-agent-field-notes) - visible notes in

/agent-notes.

- [QA Work Board](#qa-work-board)

- [List Work Items](#list-work-items)

- [Update Work Item QA State](#update-work-item-qa-state)

- [Automation Packages V1](#automation-packages-v1)

- [List Automation Packages](#list-automation-packages)

- [Create Automation Package](#create-automation-package)

- [Create Automation Package Version](#create-automation-package-version)

- [Link Automation Assertion to Test Case](#link-automation-assertion-to-test-case)

- [List Automation Runs](#list-automation-runs)

- [Queue Automation Run](#queue-automation-run)

- [API Testing V1](#api-testing-v1)

- [Create API Target](#create-api-target)

- [Create API Auth Profile](#create-api-auth-profile)

- [Create API Test Case](#create-api-test-case)

- [Run API Test Case](#run-api-test-case)

- [Mobile Testing V1](#mobile-testing-v1)

- [Create Mobile Device](#create-mobile-device)

- [Create Mobile Test Case](#create-mobile-test-case)

- [Run Mobile Test Case](#run-mobile-test-case)

Product model:

- [Application Map](#application-map)

- [List Application Map](#list-application-map)

- [Upsert Application Map Node](#upsert-application-map-node)

- [Upsert Application Map Edge](#upsert-application-map-edge)

- [Link Test Case To Application Map Node](#link-test-case-to-application-map-node)

- [Generate Application Map From Test Cases](#generate-application-map-from-test-cases)

Test cases and evidence:

- [Test Suites](#test-suites)

- [List Test Cases](#list-test-cases)

- [Create Test Case](#create-test-case)

- [Update Test Case](#update-test-case)

- [Upload Test Case Screenshot](#upload-test-case-screenshot)

Runs and results:

- [List Test Runs](#list-test-runs)

- [Run Plan](#run-plan)

- [Create Run](#create-run)

- [Update Run](#update-run)

- [Add Result](#add-result)

- [Create Triage Action](#create-triage-action)

Flow

1. Agent requests access with agent-request-access.

2. User opens the returned activationUrl, for example /activate?claim=....

3. User signs in when needed and the activation page approves the claim.

4. Agent polls agent-exchange-approval with requestId and requestSecret.

5. Agent uses the returned token to find or create the QAA project first, then

read workspace context, read test cases/runs, claim queued work requests,

write QA runs/results, and report triage decisions.

Request Access

curl -X POST "$SUPABASE_URL/functions/v1/agent-request-access" \
  -H "Content-Type: application/json" \
  -d '{
    "agentName": "Playwright QA Agent",
    "userEmail": "user@example.com",
    "appOrigin": "https://qa.felipeotarola.com",
    "scopes": ["projects:read", "projects:write", "test_cases:write", "test_runs:write", "test_results:write", "qa_manager:write"]
  }'

The response contains requestId, requestSecret, claim, and

activationUrl. The agent should show the app-domain activation URL to the

user. A projectId may still be provided to request access to only one project;

when omitted, the approved token can list available QA projects.

Exchange Approval

curl -X POST "$SUPABASE_URL/functions/v1/agent-exchange-approval" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "REQUEST_UUID",
    "requestSecret": "qa_req_..."
  }'

Pending approvals return 202. Approved requests return agentToken.

List Projects

curl -X POST "$SUPABASE_URL/functions/v1/qa-list-projects" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json"

Requires projects:read. Returns active projects unless the token is scoped to

one project, in which case only that project is returned.

Create Project

curl -X POST "$SUPABASE_URL/functions/v1/qa-create-project" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Storefront",
    "key": "acme-storefront",
    "publicSlug": "acme-storefront",
    "description": "Public storefront regression suite",
    "baseUrl": "https://acme.example.com",
    "status": "Active"
  }'

Requires projects:write. Project-scoped agent tokens cannot create projects;

the token must be workspace-wide. If key is omitted, the endpoint creates a

slug from name. If publicSlug is omitted, the endpoint uses the project key.

status defaults to Active. The endpoint also accepts slug, public_slug,

publicUrlSlug, public_url_slug, and publicProjectSlug as public slug

aliases.

Response shape:

{
  "project": {
    "id": "PROJECT_UUID",
    "name": "Acme Storefront",
    "key": "acme-storefront",
    "description": "Public storefront regression suite",
    "base_url": "https://acme.example.com",
    "status": "Active",
    "visibility": "private",
    "public_slug": "acme-storefront",
    "public_share_title": "Acme Storefront QA Report",
    "public_share_description": "Read-only QA report for Acme Storefront.",
    "public_allow_indexing": false,
    "created_at": "2026-06-16T17:00:00.000Z",
    "updated_at": "2026-06-16T17:00:00.000Z"
  }
}

Use this endpoint as the first durable setup step when the agent is working on

a project that does not already exist in QAA. Creating the project first gives

the agent a stable projectId for workspace context, test data, application

map nodes, test cases, runs, results, defects, and field notes. For existing

projects, call qa-list-projects first and reuse the matching id.

Project Brief

Use qa-agent-project-brief immediately after selecting or creating the QAA

project. This is the project-first read model for Sladdis: one call returns the

QAA state needed to decide whether the project is safe to release, what needs

attention, and what should be tested next.

curl -X POST "$SUPABASE_URL/functions/v1/qa-agent-project-brief" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID"
  }'

Requires projects:read. If the token is scoped to one project, projectId is

optional and any other project id is rejected.

The signed-in app route is also available:

curl "https://qa.felipeotarola.com/api/qa-project-brief?projectId=PROJECT_UUID" \
  -H "Cookie: authenticated_app_session=..."

Response shape:

{
  "brief": {
    "projectId": "PROJECT_UUID",
    "project": {
      "id": "PROJECT_UUID",
      "name": "Acme Storefront",
      "key": "acme-storefront"
    },
    "decision": {
      "releaseState": "blocked",
      "testConfidence": 78,
      "confidenceDelta": null,
      "blockingCount": 2,
      "advisoryCount": 1,
      "dataQualityWarningCount": 0,
      "lastVerifiedAt": "2026-06-16T17:00:00.000Z",
      "freshnessState": "fresh",
      "ageHours": 2
    },
    "attention": [
      {
        "id": "coverage-checkout",
        "type": "coverage_gap",
        "impact": "release_blocking",
        "priority": "critical",
        "title": "Checkout",
        "reason": "critical gap ยท 1 linked tests",
        "href": "/application-map"
      }
    ],
    "context": {
      "product_profile": "Public storefront for trial signups.",
      "key_flows": ["Homepage", "Signup", "Pricing"],
      "risk_areas": ["Lead capture", "Mobile navigation"]
    },
    "readiness": {
      "safeTestDataCount": 1,
      "openRetestCount": 2,
      "contextSignalCount": 7
    },
    "coverage": {
      "counts": {
        "covered_passing": 4,
        "covered_failing": 1,
        "partial": 2,
        "untested": 3,
        "blocked": 1,
        "critical_gap": 1
      },
      "gaps": [
        {
          "label": "Checkout",
          "coverageState": "covered_failing",
          "priority": "high",
          "failedCount": 2,
          "testCaseIds": ["ACME-CHECKOUT-001"]
        }
      ]
    },
    "tests": {
      "total": 27,
      "statusCounts": {
        "Passed": 18,
        "Failed": 7,
        "Blocked": 1,
        "Not run": 1
      },
      "top": []
    },
    "runs": {
      "health": {
        "releaseHealth": "failing",
        "totals": {
          "runs": 7,
          "failedRuns": 5,
          "blockedTests": 1,
          "failingTests": 9
        }
      },
      "recentFailures": []
    },
    "work": {
      "activeItems": [],
      "activeDefects": []
    },
    "retests": {
      "open": [],
      "totalOpen": 2,
      "totalRetesting": 0
    },
    "runPlans": {
      "active": [],
      "recentCompleted": []
    },
    "notes": {
      "recent": [],
      "memory": [],
      "nextTest": []
    }
  }
}

The brief intentionally composes existing QAA primitives instead of creating a

parallel intelligence store. Do not create duplicate "project intelligence",

"coverage heatmap", or "memory" tables before this brief shows a real schema

gap. Current source-of-truth mapping:

- Project/product facts: qa_project_context.

- Test data and safe rules: qa_test_data and qa_project_context.

- Coverage heatmap: qa_app_map_nodes, qa_test_case_app_nodes, and latest

test status.

- Execution health: qa_test_runs and qa_test_run_results.

- Reproducible evidence: qa_test_evidence, qa_api_run_evidence, and

qa_mobile_run_evidence.

- Bugs/action handoff: qa_work_items with item_type = bug.

- Retests: qa_retest_items.

- Saved pre-run intent and plan-vs-actual comparison: qa_run_plans.

- Narrative, memory, and next-test notes: qa_agent_field_notes.

- Triage/audit decisions: qa_triage_actions and qa_agent_audit_logs.

The /qa-overview app page should become Project Intelligence rather than a

separate overview dashboard. It should be built from this brief and show the

product profile, key flows, risk areas, known weak spots, coverage map, active

defects, retest queue, active run plans, and confidence score per area. Keep

QAA as the data layer and review surface; Sladdis should do the reasoning and

write the resulting plan, evidence, and notes back into these primitives.

The recommended rollout is:

1. Use this brief as Sladdis' first read after project selection.

2. Build coverage heatmap UI from brief.coverage.

3. Use Retest Queue V2 links on qa_retest_items: test_case_id, run_id,

result_id, work_item_id, resolved_by_run_id, and resolved_at.

4. Save pre-run intent in qa_run_plans before execution and compare it with

the linked qa_test_runs actual outcome after execution.

5. Generate next-best-test recommendations from the brief first; persist them

later only if history/review becomes useful.

QA Workspace

QAA is the system of record for project Quality Assurance. If an agent has QAA

access, it should read project workspace context before testing and persist

workspace updates after testing. Do not only report findings in chat when a QAA

project is available.

The workspace contains durable project context, safe test data, agent

instructions, and retest queue items. This is what lets Sladdis act as the QA

owner for a project instead of a one-off URL checker.

Use qa-create-agent-note in [Create Agent Field Notes](#create-agent-field-notes)

for all QA narrative, observations, reasoning, and memory that should appear in

/agent-notes.

### Read QA Workspace

curl -X GET "$SUPABASE_URL/functions/v1/qa-workspace?projectId=PROJECT_UUID" \
  -H "Authorization: Bearer qa_agent_..."

Requires projects:read. Returns context, testData, and retestItems.

Agents should call this before planning a run.

Response shape:

{
  "projectId": "PROJECT_UUID",
  "context": {
    "project_id": "PROJECT_UUID",
    "product_profile": "Public storefront for trial signups.",
    "key_flows": ["Homepage", "Signup", "Pricing", "Contact"],
    "risk_areas": ["Auth", "Lead capture", "Mobile navigation"],
    "testing_rules": [
      "Do not submit real orders",
      "Stop before payment capture"
    ],
    "agent_instructions": "Run safe smoke first, persist evidence, queue retests for failures.",
    "updated_by": "Sladdis"
  },
  "testData": [
    {
      "id": "TEST_DATA_UUID",
      "label": "Login test user",
      "data_type": "credential",
      "sensitivity": "sensitive",
      "usage_notes": "Use only for login smoke testing when explicitly requested.",
      "status": "active"
    }
  ],
  "retestItems": [
    {
      "id": "RETEST_UUID",
      "title": "Retest checkout validation after fix",
      "status": "open",
      "priority": "high",
      "source_type": "result",
      "source_id": "RUN_RESULT_UUID"
    }
  ]
}

The app-session route is also available for the signed-in UI:

curl "https://qa.felipeotarola.com/api/qa-workspace?projectId=PROJECT_UUID" \
  -H "Cookie: authenticated_app_session=..."

### Upsert Project Context

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "upsert-context",
    "projectId": "PROJECT_UUID",
    "productProfile": "Public storefront for trial signups.",
    "keyFlows": "Homepage\nSignup\nPricing\nContact",
    "riskAreas": "Auth\nLead capture\nMobile navigation",
    "testingRules": "Do not submit real orders\nStop before payment capture",
    "agentInstructions": "Run safe smoke first, persist evidence, queue retests for failures."
  }'

Requires test_cases:write.

### Create Test Data And Retest Items

Use the same endpoint with action set to create-test-data,

create-retest-item, or update-retest-status. Test data must be classified as

safe, sensitive, temporary, or do_not_reuse. Retest status values are

open, retesting, resolved, and ignored.

For notes, observations, reasoning, and project memory, use the separate

qa-create-agent-note Edge Function so the content appears in /agent-notes.

Retest Queue V2:

- Link every retest to the failed evidence when possible: testCaseId,

runId, resultId, and workItemId.

- Use sourceType: "result" and sourceId: "RUN_RESULT_UUID" when the retest

comes from a failed or blocked result.

- When a retest passes, call update-retest-status with status: "resolved"

and resolvedByRunId. QAA stores resolved_at and can show "fixed after run

X".

- Keep qa_work_items as the bug/action handoff and qa_retest_items as the

verification queue. Do not create a second retest model in Agent Notes or

triage actions.

Example: saving credentials or other user-provided test data:

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-test-data",
    "projectId": "PROJECT_UUID",
    "label": "Login test user",
    "dataType": "credential",
    "value": "email=test-user@example.com\npassword=EXAMPLE_ONLY_DO_NOT_USE",
    "sensitivity": "sensitive",
    "usageNotes": "Example only. Store real credentials only when the user explicitly provides test credentials and the project rules allow authenticated checks."
  }'

Example: creating a linked retest from a failed result:

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-retest-item",
    "projectId": "PROJECT_UUID",
    "title": "Retest checkout validation after fix",
    "reason": "Checkout failed at step 2 with validation error.",
    "priority": "critical",
    "sourceType": "result",
    "sourceId": "RUN_RESULT_UUID",
    "testCaseId": "ACME-CHECKOUT-001",
    "runId": "run-checkout-regression",
    "resultId": "RUN_RESULT_UUID",
    "workItemId": "WORK_ITEM_UUID"
  }'

Example: resolving a retest after a passing rerun:

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "update-retest-status",
    "projectId": "PROJECT_UUID",
    "id": "RETEST_UUID",
    "status": "resolved",
    "resolvedByRunId": "run-checkout-rerun",
    "resolutionNotes": "Checkout validation passed after deploy."
  }'

When a user tells the agent "this is test data", the agent should save it here

before or after the run, classify sensitivity conservatively, and reference it

in future project QA planning.

Agent rules for test data:

- Default to sensitive for credentials, tokens, personal data, customer data,

production identifiers, or anything that could grant access.

- Use temporary for values the agent creates during a single run and may

delete or ignore after the run.

- Use do_not_reuse for one-time codes, expired links, destructive data, or

user-provided values that should not be tried again.

- Do not paste sensitive test data into Slack, public reports, screenshots,

defect titles, or run summaries. Reference the test data by label or id

instead.

- If the user says "this is test data", save it to the workspace before or

immediately after the run, unless doing so would store secrets outside the

agreed QAA boundary.

- Never invent real credentials. Generated credentials must be clearly marked as

fake, temporary, or example-only.

QA Work Board

The QA Work Board is the human-to-agent delivery board in QAA. It is separate

from durable test coverage: humans own the product scope, title, description,

priority, due date, and acceptance criteria; the agent reads those fields,

chooses relevant QAA tests, runs them, and writes back QA state.

Board statuses:

- backlog

- in_progress

- ready_for_qa

- testing

- tested

- done

Agents should normally start from ready_for_qa, move a card to testing

while execution is running, then move it to tested or back to in_progress

with a concise QA summary and linked run id. When QA finds a bug, the ticket is

not complete: set qaStatus to failed, write the bug fields, and move

status back to in_progress. If the agent cannot test because of a blocker,

set qaStatus to blocked, explain the blocker in qaSummary, and move or

leave status as in_progress. done is the final accepted state after the

work has passed QA and been handed off.

Agent-writable fields:

- status

- qaStatus

- qaRunId

- qaSummary

- qaRisk

- qaAgentNotes

- agentPrompt

- bugTitle

- bugDescription

- bugSeverity

- releaseImpact

- releaseImpactReason

Do not edit title, description, acceptance criteria, priority, owner, due date,

or product scope from the agent.

### List Work Items

curl -X POST "$SUPABASE_URL/functions/v1/qa-list-work-items" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "status": "ready_for_qa",
    "limit": 50
  }'

Requires projects:read. If the token is scoped to one project, projectId

is optional and any other project id is rejected. Supported filters:

projectId, status, qaStatus, itemType, search, and limit.

Response shape:

{
  "workItems": [
    {
      "id": "WORK_ITEM_UUID",
      "project_id": "PROJECT_UUID",
      "title": "Mobile login smoke reaches welcome screen",
      "description": "Run Android real-device smoke for package com.example.admin.",
      "status": "ready_for_qa",
      "priority": "high",
      "item_type": "qa",
      "acceptance_criteria": [
        "App launches on a safe Android device",
        "Welcome text appears after login"
      ],
      "target_url": "com.example.admin",
      "app_area": "Mobile Auth",
      "linked_test_case_ids": [],
      "qa_status": "not_tested",
      "qa_run_id": null,
      "qa_summary": "",
      "qa_risk": "medium",
      "release_impact": "informational",
      "release_impact_reason": "",
      "qa_agent_notes": "",
      "agent_prompt": ""
    }
  ]
}

### Update Work Item QA State

curl -X PATCH "$SUPABASE_URL/functions/v1/qa-update-work-item" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "id": "WORK_ITEM_UUID",
    "projectId": "PROJECT_UUID",
    "status": "in_progress",
    "qaStatus": "failed",
    "qaRunId": "run-mobile-login",
    "qaSummary": "Login smoke failed because Welcome text never appeared.",
    "qaRisk": "high",
    "qaAgentNotes": "Reproduced on Android staging device. Screenshot evidence is attached to the run result.",
    "agentPrompt": "You are fixing a QA failure. Reproduce the Android login smoke, inspect the auth flow, implement the smallest fix, add regression coverage, rerun QAA, and update this ticket.",
    "bugTitle": "Android login does not reach Welcome screen",
    "bugDescription": "After tapping Login with approved test data, the app remains on the login screen without a crash signal.",
    "bugSeverity": "P2",
    "releaseImpact": "non_blocking",
    "releaseImpactReason": "The defect needs repair but does not invalidate the current release-critical flows."
  }'

Requires test_results:write. Allowed agent transitions are intentionally

limited to QA handoff movement:

- backlog -> in_progress | ready_for_qa

- in_progress -> ready_for_qa

- ready_for_qa -> testing | in_progress

- testing -> ready_for_qa | in_progress | tested

- tested -> ready_for_qa | testing | done

- done -> ready_for_qa | testing

Use qaRunId whenever the update is based on a QAA run. If a bug is found,

move the card back to in_progress, set qaStatus to failed, and write a

short bugTitle, reproducible bugDescription, and severity P1, P2, P3,

or P4. agentPrompt is the repair prompt for a coding agent: include the

failed behavior, reproduction target, expected outcome, and required checks,

but do not include secrets, raw logs, or sensitive screenshot contents. Attach

redacted evidence to the run instead. If the test is blocked, use

status: "in_progress" with qaStatus: "blocked" rather than a board column.

Set releaseImpact explicitly to release_blocking, non_blocking,

data_quality, or informational, and explain the decision in

releaseImpactReason. Do not infer release impact from severity alone when the

product context provides a more precise answer.

Every work item update creates an append-only qa_work_item_events history row

with the changed fields, previous values, new values, actor, and source. Do not

erase older bug feedback to describe a new retest. Update the current summary

fields with the latest state; QAA preserves prior developer fixes, failed

retests, severity changes, and handoff movement in ticket history.

Automation Packages V1

Automation packages are versioned, project-linked source assets owned by QAA.

V1 supports QAA-managed Playwright drafts, code inspection, and zip export.

The web runner capability is configured when the isolated host worker can

claim validation jobs. Agents must still use the persisted run result rather

than treating a queued or running draft as executed.

Core rules:

- Match or create the QAA project before creating a package.

- Use playwright for QAA-managed V1 packages.

- Keep package paths relative and bounded; never include secrets.

- Begin at draft. Validation and execution are separate capabilities.

- Treat queued/running packages as not-run; only a persisted terminal run is evidence.

- Treat browser load checks as bounded capacity probes, not unrestricted stress

tests. Use a separate package, read-only navigation, one queued run initially,

no authenticated or mutating flows, and explicit concurrency/request limits.

- Record the runner container limits with the result. Do not present a

container-limited run as whole-host capacity evidence.

- The default ceiling for an initial capacity probe is four concurrent pages,

25 total navigations, 30 seconds per test, and one run. Raising any ceiling

requires reviewing target ownership, worker headroom, and stop conditions.

- Stop escalation on timeouts, HTTP 429/5xx responses, runner blocking, OOM,

swap growth, or sustained host pressure.

- Requires projects:read to list and test_cases:write to create.

- Sladdis owns package selection, queueing, and interpretation. The isolated

host worker only claims and executes the persisted job.

- GitHub export is a reviewed external mutation. A project owner/admin connects

a repository token in QAA, where it is encrypted in Supabase Vault and never

returned to an agent or browser. Sladdis may prepare the immutable package

version and target path, but a signed-in project member must explicitly

approve creation of the branch and pull request in QAA.

- QAA remains the system of record for QA history and evidence. After an

exported pull request is merged, Git is the source of truth for that

repository-managed test source.

- PR, deployment, and schema triggers are persisted disabled by default. An

owner/admin receives the signing secret once, configures the provider

webhook, and can activate only after the latest three terminal package runs

all passed. Webhooks are HMAC-SHA256 verified and delivery-id deduplicated.

Agents must not activate triggers or handle their signing secrets.

### List Automation Packages

curl "$SUPABASE_URL/functions/v1/qa-automation-packages?projectId=PROJECT_UUID" \
  -H "Authorization: Bearer qa_agent_..."

Pass id=PACKAGE_UUID to read one package including its latest version and

source files.

### Create Automation Package

curl -X POST "$SUPABASE_URL/functions/v1/qa-automation-packages" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "name": "Public site smoke",
    "description": "Stable read-only checks",
    "targetUrl": "https://example.com",
    "framework": "playwright",
    "files": [
      {
        "path": "playwright.config.ts",
        "mediaType": "text/typescript",
        "content": "import { defineConfig } from \"@playwright/test\"; export default defineConfig({ workers: 1 });"
      },
      {
        "path": "tests/homepage.spec.ts",
        "mediaType": "text/typescript",
        "content": "import { test, expect } from \"@playwright/test\"; test(\"home\", async ({ page }) => { await page.goto(\"https://example.com\"); await expect(page).toHaveTitle(/Example/); });"
      }
    ]
  }'

The response returns the immutable draft version and

runnerCapability: "configured". Human users can inspect and download the

same package from /automation-packages.

### Create Automation Package Version

Create an immutable next revision of an existing QAA-managed package:

curl -X POST "$SUPABASE_URL/functions/v1/qa-automation-packages" \
  -H "Authorization: Bearer $QAA_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-version",
    "projectId": "PROJECT_UUID",
    "packageId": "PACKAGE_UUID",
    "baseVersionId": "CURRENT_VERSION_UUID",
    "changelog": "Add navigation regression coverage",
    "files": [{"path":"tests/navigation.spec.ts","mediaType":"text/typescript","content":"..."}]
  }'

baseVersionId is an optimistic concurrency guard. QAA rejects stale edits and

no-op revisions. Success creates vN+1; it never modifies earlier source,

hashes, runs, or artifacts. New runs pin the latest version while historical

runs remain pinned to the version they executed.

### Link Automation Assertion to Test Case

Link an exact Playwright file::title key to an existing QAA test case:

curl -X POST "$SUPABASE_URL/functions/v1/qa-automation-packages" \
  -H "Authorization: Bearer $QAA_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "link-test-case",
    "projectId": "PROJECT_UUID",
    "packageId": "PACKAGE_UUID",
    "testCaseId": "WEB-HOME-001",
    "testKey": "tests/homepage.spec.ts::homepage responds"
  }'

The package and test case must belong to the same project. * is supported

only as a single-test package fallback. A terminal run materializes linked

assertions into the normal QAA test run and result history. A failure creates

one deduplicated Work Board finding plus an open retest; repeated failures

update that handoff, and a later pass resolves both the retest and finding.

### List Automation Runs

curl "$SUPABASE_URL/functions/v1/qa-automation-runs?projectId=PROJECT_UUID&packageId=PACKAGE_UUID" \
  -H "Authorization: Bearer qa_agent_..."

Requires projects:read. Treat queued and running as not-run. Only

passed, failed, or blocked is terminal evidence. A blocked run means

the execution engine or policy could not produce a valid Playwright result; it

must not be reported as a product failure.

### Queue Automation Run

curl -X POST "$SUPABASE_URL/functions/v1/qa-automation-runs" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "queue",
    "packageId": "PACKAGE_UUID"
  }'

Requires test_results:write. The runner selects the newest runnable immutable

version, applies exact-origin and resource policy, and persists status,

statistics, a bounded log excerpt, package version, target, and runner version.

It uploads private screenshots, traces, failure videos, JSON reports, and HTML

reports when Playwright produces them. Every artifact is project/run scoped,

size and type limited, SHA-256 verified after upload, inventoried in

qa_automation_run_artifacts, and available to signed-in project members from

/automation-packages. Do not claim an artifact exists unless it appears on

the persisted terminal run.

API Testing V1

API testing is a first-class test type beside web testing. Agents must not use

free-form fetch access for project APIs. API requests should be represented as

API targets, auth profiles, API test cases, and controlled runner executions so

QAA owns safety policy, auth injection, redaction, and evidence persistence.

Core rules:

- Create an API target before creating API cases.

- Store only secret references in QAA. Do not write secret values into test

data, notes, case bodies, or reports.

- Production mutation policy should be read_only or approval_required.

- Runner evidence is redacted before persistence.

- Public reports only show redacted API summaries.

Mutation policies:

- read_only: only GET, HEAD, and OPTIONS.

- safe_mutations: mutations only when the target is marked safe.

- approval_required: mutations require explicit human approval.

- blocked: mutations are never allowed.

Response body storage:

- none: store no body.

- excerpt: store a redacted excerpt.

- full_redacted: store full redacted body only when the target is marked as a

safe environment; otherwise the runner downgrades to excerpt.

### Create API Target

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-api-target",
    "projectId": "PROJECT_UUID",
    "name": "Customer API",
    "environment": "staging",
    "baseUrl": "https://api.example.com/v1",
    "mutationPolicy": "approval_required",
    "allowedMethods": "GET\nHEAD\nOPTIONS",
    "forbiddenPaths": "/admin\n/billing",
    "storeResponseBody": "excerpt",
    "timeoutMs": "15000",
    "isSafeEnvironment": "false"
  }'

### Create API Auth Profile

Auth profiles store metadata and secret references only. Secret values are

resolved by the runner environment.

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-api-auth-profile",
    "projectId": "PROJECT_UUID",
    "name": "Staging bearer token",
    "authType": "bearer",
    "secretRef": "staging_api_token",
    "placement": "header",
    "headerName": "Authorization"
  }'

### Create API Test Case

Creates a normal QAA test case with test_type = api and the linked

qa_api_test_cases runner details.

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-api-test-case",
    "projectId": "PROJECT_UUID",
    "targetId": "API_TARGET_UUID",
    "title": "Customer API returns current profile",
    "area": "Customers API",
    "method": "GET",
    "path": "/customers/me",
    "expectedStatus": "200",
    "authMode": "target_default",
    "queryTemplate": "{}",
    "headersTemplate": "{\"accept\":\"application/json\"}",
    "assertions": "[{\"type\":\"status_equals\",\"value\":200},{\"type\":\"json_path_exists\",\"path\":\"data.id\"}]",
    "steps": [
      {
        "action": "Send GET request to /customers/me.",
        "expected": "The request is executed against the configured API target policy."
      },
      {
        "action": "Validate the HTTP response status and response payload.",
        "expected": "Status is 200 and JSON path data.id exists."
      }
    ]
  }'

If steps is omitted, QAA creates a small default API procedure from the

stored method, path, expected status, and assertions. Prefer sending explicit

steps when the case documents business meaning beyond a raw endpoint check.

### Run API Test Case

The Edge Function runs the controlled API runner, applies QAA target policy,

persists the run result, and stores redacted API evidence. Agents must not send

URL, method, headers, body, or auth secrets to this endpoint. The runner loads

those details from the saved QAA API target and API test case.

curl -X POST "$SUPABASE_URL/functions/v1/qa-run-api-test-case" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "testCaseId": "API-CUSTOMER-...",
    "runId": "RUN_ID",
    "approvedMutation": false
  }'

Requires test_results:write. Project-scoped tokens can only run API test

cases for their own project. Failed API runner results create the same linked

QA Work Board bug ticket and agent_prompt as qa-add-result. The response

includes a compact runner summary:

{
  "result": { "status": "Passed" },
  "workItem": null,
  "workItemError": null,
  "autoTicket": {
    "functionVersion": "qa-run-api-test-case:auto-ticket-v2",
    "normalizedStatus": "Passed",
    "attempted": false,
    "createdOrReused": false,
    "error": null
  },
  "runner": {
    "request": {
      "method": "GET",
      "url": "https://api.example.com/customers/me?api_key=%5BREDACTED%5D"
    },
    "response": { "status": 200, "bodyStorage": "excerpt" },
    "assertions": [
      { "type": "status_equals", "passed": true, "message": "Status was 200." }
    ]
  }
}

The signed-in app route may still exist as a UI-only/internal runner surface,

but agent automation should use the Edge Function above.

Runner safety checks:

- Method whitelist is strict.

- Paths must be relative to the target base URL and cannot be absolute or

protocol-relative.

- Redirects are not followed automatically.

- Bodies are not sent for GET or HEAD.

- Assertions run even when the expected status is non-2xx.

- Headers, query values, request bodies, response headers, and response bodies

are redacted before persistence.

Mobile Testing V1

Mobile testing is a first-class test type beside web and API testing. QAA owns

the project configuration, safe device policy, mobile test case metadata, run

status, and redacted evidence. The ADB execution engine lives in the harness or

agent runtime, not in the app UI.

Agents must not run arbitrary ADB commands from QAA. A mobile run is limited to

the stored mobile test case contract and the configured device policy.

Core rules:

- Create a mobile device before creating mobile cases.

- Only Android is supported in V1.

- Do not require Appium or Playwright for the first mobile path.

- Devices must be enabled and is_safe_device = true before execution.

- App packages must match the device allowed_packages list when configured.

- Raw screenshots, UIAutomator dumps, and logcat output belong in the harness

first. QAA stores only paths and redacted excerpts.

- Harness-provided mobile screenshot URLs are mirrored into the same

qa_test_evidence gallery used by web runs when they are public-renderable

http(s) artifact URLs.

- Public reports show only a redacted mobile summary by default. Do not expose

logcat, UI dumps, secrets, or unapproved screenshots publicly.

Supported mobile steps:

[
  { "type": "tap", "target": "text=Login" },
  { "type": "tap", "x": 120, "y": 600 },
  { "type": "text", "valueRef": "test_user_email" },
  { "type": "swipe", "direction": "up" },
  { "type": "press", "key": "BACK" },
  { "type": "wait_for_text", "value": "Welcome" },
  { "type": "screenshot" }
]

Harness implementations should prefer UIAutomator selectors from text,

resource-id, and content-desc before falling back to coordinates.

Supported mobile assertions:

[
  { "type": "text_visible", "value": "Welcome" },
  { "type": "text_not_visible", "value": "Crash" },
  { "type": "element_exists", "selector": "resource-id=com.app:id/login" },
  { "type": "current_package_equals", "value": "com.example.app" },
  { "type": "no_crash_in_logcat" },
  { "type": "screen_changed" }
]

### Create Mobile Device

Use the workspace endpoint to register a safe Android test device and policy.

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-mobile-device",
    "projectId": "PROJECT_UUID",
    "name": "Pixel staging device",
    "deviceSerial": "emulator-5554",
    "model": "Pixel 8",
    "osVersion": "Android 15",
    "appPackage": "com.example.app",
    "environment": "staging",
    "isSafeDevice": "true",
    "allowedPackages": "com.example.app\ncom.example.app.staging",
    "forbiddenActions": "",
    "notes": "Dedicated staging Android device. Do not use against production accounts."
  }'

### Create Mobile Test Case

Creates a normal QAA test case with test_type = mobile and the linked

qa_mobile_test_cases runner details.

curl -X POST "$SUPABASE_URL/functions/v1/qa-workspace" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "create-mobile-test-case",
    "projectId": "PROJECT_UUID",
    "deviceId": "DEVICE_UUID",
    "title": "Android login smoke reaches welcome screen",
    "area": "Mobile Auth",
    "appPackage": "com.example.app",
    "launchMode": "package",
    "preconditions": "{\"account\":\"test_user_email\"}",
    "steps": "[{\"type\":\"tap\",\"target\":\"text=Login\"},{\"type\":\"text\",\"valueRef\":\"test_user_email\"},{\"type\":\"tap\",\"target\":\"text=Continue\"},{\"type\":\"wait_for_text\",\"value\":\"Welcome\"}]",
    "assertions": "[{\"type\":\"text_visible\",\"value\":\"Welcome\"},{\"type\":\"no_crash_in_logcat\"}]",
    "safetyLevel": "safe_interaction",
    "timeoutMs": "60000"
  }'

Safety levels:

- read: capture screen/UI tree/log signals only.

- safe_interaction: taps, text input using approved test data, swipes, back,

waits, and screenshots.

- destructive: requires explicit human approval and should normally be

avoided in shared environments.

### Run Mobile Test Case

The signed-in app exposes a runner contract at:

curl -X POST "$APP_URL/api/qa-mobile-runner" \
  -H "Cookie: authenticated_app_session=..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "testCaseId": "MOB-LOGIN-...",
    "deviceId": "DEVICE_UUID",
    "approvedInteraction": false
  }'

The app validates QAA policy and then calls the mobile harness when

QA_MOBILE_HARNESS_URL is configured. Without a configured harness, the runner

blocks safely and persists redacted evidence explaining that no ADB execution

occurred.

Harness input shape:

{
  "deviceSerial": "emulator-5554",
  "appPackage": "com.example.app",
  "launch": { "mode": "package", "value": null },
  "safetyLevel": "safe_interaction",
  "timeoutMs": 60000,
  "steps": [],
  "assertions": []
}

Harness response shape:

{
  "status": "passed",
  "durationMs": 12340,
  "assertionResults": [
    {
      "type": "text_visible",
      "passed": true,
      "message": "Welcome was visible."
    }
  ],
  "screenshotBeforePath": "https://...",
  "screenshotAfterPath": "https://...",
  "uiDumpExcerpt": "redacted UIAutomator excerpt",
  "logcatExcerpt": "redacted crash/log signal excerpt",
  "blockedReason": null,
  "errorMessage": null
}

Agents should treat mobile screenshots and logcat as sensitive. Store only

redacted excerpts and stable artifact paths returned by the harness. Use

Application Map screenshots for stable product surfaces, test case screenshots

for expected reference states, and run evidence screenshots for what happened

during a specific execution.

Application Map

The Application Map is the diagram workspace for how the system behaves, where tests

attach, and where risk lives. It is separate from executable test cases. Use it

to describe pages, components, customer flows, API calls, repo/service

dependencies, external systems, and important UI states.

Agents should write durable nodes and edges first. The UI can render those

records as a heatmap, Mermaid-style diagrams, app/customer/API/repo flows, and

coverage-shape charts. Link test cases to map nodes only when a case actually

covers or validates that surface. Stakeholders use this map to see product

structure, missing coverage, risky dependencies, and visual context.

Recommended node/edge modeling:

- Build structured graph records first. Mermaid, React Flow, and other diagrams

are renderers/export views, not the source of truth.

- App behavior layer (layer: "application"): page/state/component/flow nodes

connected by navigation, redirect, modal, or contains edges.

- API layer (layer: "api"): api_endpoint, api_operation, service, and

external_service nodes connected by calls, api, or dependency edges.

Include auth requirements, methods, status families, mutation policy, and

redaction notes in metadata.

- Data layer (layer: "data"): data_entity, data_store, and

database_table nodes connected by reads, writes, owns, or

dependency edges. Include sensitive fields, ownership, source, and evidence

in metadata.

- Coverage layer (layer: "coverage"): test_case and coverage_area nodes

connected to application/API/data nodes conceptually through covers,

validates, blocks, or related edges. Continue using

qa-link-test-case-to-app-node for the durable test-to-surface link.

- Test attachment: link test cases to the node they prove with

qa-link-test-case-to-app-node.

- Risk: set riskLevel and coverageStatus from latest results, known

defects, blockers, missing evidence, and untested critical paths.

- Discovery expectation: agents should populate these layers from multiple

observations when available: browser walkthroughs, public routes, network

calls, API docs/OpenAPI, saved QAA API targets, database/schema metadata,

screenshots, prior runs, work items, and retest queue history. Do not only

mirror the same app map in a different format.

### List Application Map

curl -X POST "$SUPABASE_URL/functions/v1/qa-list-app-map" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID"
  }'

Requires projects:read. Returns nodes, edges, and links. The UI derives

heatmaps, Mermaid-style source, risk paths, and coverage-shape metrics from

these records.

### Upsert Application Map Node

curl -X POST "$SUPABASE_URL/functions/v1/qa-upsert-app-map-node" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "layer": "application",
    "key": "login-page",
    "label": "Login page",
    "kind": "page",
    "route": "/login",
    "url": "https://example.com/login",
    "description": "Authentication entry point for returning users.",
    "riskLevel": "critical",
    "coverageStatus": "partial",
    "position": { "x": 120, "y": 80 },
    "screenshots": [
      {
        "url": "https://...public.blob.vercel-storage.com/qa/project/login.png",
        "label": "Login page",
        "targetUrl": "https://example.com/login",
        "kind": "page",
        "capturedAt": "2026-06-18T10:20:00.000Z",
        "viewport": { "width": 1440, "height": 900, "device": "desktop" }
      }
    ],
    "metadata": {
      "authRequired": false,
      "discoveredBy": "playwright"
    }
  }'

Requires test_cases:write. key must be stable within the project layer. If a

node with the same projectId, layer, and key exists, it is updated.

Supported layer values: application, api, data, and coverage.

Supported kind values: page, component, flow, service, external,

state, api_endpoint, api_operation, data_entity, data_store,

database_table, external_service, test_case, and coverage_area.

Supported riskLevel values: low, medium, high, and critical.

Supported coverageStatus values: unknown, missing, partial, covered,

and failing.

The endpoint also accepts "nodes": [...] for bulk upsert. To attach visual

context, first upload a screenshot with /api/qa-screenshots, then copy the

returned screenshot object into screenshots.

### Upsert Application Map Edge

curl -X POST "$SUPABASE_URL/functions/v1/qa-upsert-app-map-edge" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "layer": "application",
    "sourceKey": "login-page",
    "targetKey": "dashboard",
    "kind": "navigation",
    "label": "Successful login",
    "metadata": {
      "trigger": "submit valid credentials"
    }
  }'

Requires test_cases:write. Use sourceKey/targetKey for stable agent

writes, or sourceNodeId/targetNodeId if ids are already known. Edges default

to resolving keys inside their own layer; use sourceLayer or targetLayer

when linking across layers. Supported kind values: navigation, redirect,

dependency, api, modal, contains, related, calls, reads, writes,

owns, covers, validates, and blocks.

Use edge kinds consistently:

- navigation: user can move from one surface to another.

- redirect: the system sends the user to another surface.

- modal: one surface opens or controls a transient state.

- contains: parent flow/page owns a component or state.

- api: frontend or service calls another service or endpoint.

- calls: one API operation calls another API/service operation.

- reads: a UI/API/service reads a data entity, store, or table.

- writes: a UI/API/service mutates a data entity, store, or table.

- owns: a service or domain owns a data entity or table.

- covers: a test or coverage area exercises a surface.

- validates: a test validates a surface, contract, or invariant.

- blocks: a failure, defect, or dependency blocks confidence in a surface.

- dependency: repo/service/component depends on another unit.

- related: weak relationship when context exists but a hard flow is unknown.

### Link Test Case To Application Map Node

curl -X POST "$SUPABASE_URL/functions/v1/qa-link-test-case-to-app-node" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "testCaseId": "POOLIO-AUTH-002",
    "nodeKey": "login-page",
    "relationship": "covers"
  }'

Requires test_cases:write. Supported relationships: covers, validates,

blocks, and mentions. Prefer covers for normal regression coverage.

### Generate Application Map From Test Cases

curl -X POST "$SUPABASE_URL/functions/v1/qa-generate-app-map-from-test-cases" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID"
  }'

Requires test_cases:write. This creates or updates one starter map node per

test-case area, links existing cases to those nodes, and infers coverage/risk

from current test statuses and priorities. Use this only as a bootstrap step;

after exploration, replace area-level nodes with concrete pages, flows,

components, services, and states.

Test Suites

Test suites are reusable, named collections of durable test cases. A test case

may belong to several suites. Suites are not execution history: a run plan

resolves the selected suites to a deduplicated test-case snapshot, and the

later run/result records remain unchanged if suite membership changes.

List the active suites for a project:

curl "$SUPABASE_URL/functions/v1/qa-test-suites?projectId=PROJECT_UUID" \
  -H "Authorization: Bearer qa_agent_..."

Create a suite:

curl -X POST "$SUPABASE_URL/functions/v1/qa-test-suites" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "name": "Authentication smoke",
    "description": "Fast release-critical checks for sign-in and sign-out.",
    "purpose": "smoke",
    "tags": ["authentication", "release-critical"],
    "testCaseIds": ["AUTH-001", "AUTH-002", "AUTH-005"]
  }'

Update the suite or replace its ordered membership:

curl -X PATCH "$SUPABASE_URL/functions/v1/qa-test-suites" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "id": "SUITE_UUID",
    "name": "Authentication smoke",
    "purpose": "smoke",
    "testCaseIds": ["AUTH-001", "AUTH-002", "AUTH-005", "AUTH-008"]
  }'

Archive a suite without changing historic runs:

curl -X DELETE "$SUPABASE_URL/functions/v1/qa-test-suites?projectId=PROJECT_UUID&id=SUITE_UUID" \
  -H "Authorization: Bearer qa_agent_..."

Reading requires projects:read; creating, updating, and archiving requires

test_cases:write. Supported purposes are smoke, regression, feature,

release, and custom.

Agent suite rules:

- Reuse an existing relevant suite instead of creating one for every run.

- Create a suite only when the collection is expected to be reused.

- A case may belong to several suites; deduplicate cases before execution.

- Leave a case outside suites when membership is unclear instead of guessing.

- Never treat a suite as a result or a run. Suite membership is organization;

results belong to a specific run.

- Never rewrite historic runs after a suite changes.

List Test Cases

curl -X POST "$SUPABASE_URL/functions/v1/qa-list-test-cases" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "status": "Failed",
    "priority": "Critical",
    "includeSteps": true,
    "includeResults": true,
    "limit": 50
  }'

Requires projects:read. If the token is scoped to one project, projectId is

optional and any other project id is rejected. The endpoint also accepts GET

query parameters, for example:

curl "$SUPABASE_URL/functions/v1/qa-list-test-cases?projectId=PROJECT_UUID&area=Checkout&includeResults=true" \
  -H "Authorization: Bearer qa_agent_..."

Supported filters: projectId, id, status, priority, area, release,

search, and limit. includeSteps defaults to true; includeResults

defaults to false. Test cases with project_id = null are treated as legacy

global/demo cases and are hidden by default; pass includeGlobal: true or

includeGlobal=true to include them.

Response shape:

{
  "testCases": [
    {
      "id": "LYS-CONTACT-006",
      "project_id": "PROJECT_UUID",
      "title": "Contact form accepts a qualified lead",
      "description": "Validates lead capture submission.",
      "area": "Lead capture",
      "status": "Failed",
      "priority": "Critical",
      "preconditions": ["Preview deployment is available"],
      "expected_result": "The lead is submitted and confirmed.",
      "steps": [
        {
          "position": 1,
          "action": "Open the contact page.",
          "expected": "The form is visible."
        }
      ],
      "results": [
        {
          "status": "Failed",
          "environment": "Production",
          "notes": "Submission returned validation error."
        }
      ]
    }
  ]
}

Use this endpoint when the agent needs to answer questions such as:

- "Which critical test cases are currently failing?"

- "Summarize the checkout test coverage and preconditions."

- "What steps should be executed for this test case?"

- "Which tests are blocked for the current release?"

Create Test Case

If the agent has captured a page or component image, upload it first with

/api/qa-screenshots and pass the returned object in screenshots. Screenshots

belong on test cases when they describe the intended page/component/state that

the case covers. Save execution failure evidence on run results later.

curl -X POST "$SUPABASE_URL/functions/v1/qa-create-test-case" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "id": "LYS-CHECKOUT-009",
    "title": "Checkout confirms successful card payment",
    "description": "Verifies that a shopper can complete payment and reach the receipt page.",
    "area": "Checkout",
    "priority": "Critical",
    "browser": "Chrome",
    "device": "Desktop",
    "status": "Not run",
    "suiteType": "regression",
    "qualityDimensions": ["functional"],
    "agentGenerated": true,
    "strategyNotes": "Durable regression coverage created from checkout release-risk analysis.",
    "tags": ["checkout", "payment", "release-critical"],
    "screenshots": [
      {
        "url": "https://...public.blob.vercel-storage.com/qa/project/case/checkout.png",
        "label": "Checkout payment form",
        "targetUrl": "https://example.com/checkout",
        "kind": "page",
        "capturedAt": "2026-06-18T10:20:00.000Z",
        "viewport": {
          "width": 1440,
          "height": 900,
          "device": "desktop"
        }
      }
    ],
    "owner": "QA Agent",
    "release": "RC-2026.06",
    "preconditions": ["Cart contains one product", "Test payment gateway is available"],
    "expectedResult": "The order is confirmed and the receipt is visible.",
    "testUrl": "https://example.com/checkout",
    "steps": [
      {
        "action": "Open checkout with a product in cart.",
        "expected": "Checkout form is visible."
      },
      {
        "action": "Submit valid payment details.",
        "expected": "Payment succeeds and receipt page opens."
      }
    ]
  }'

Requires test_cases:write. If the token is scoped to one project, projectId

is optional and any other project id is rejected. If id is omitted, the

endpoint generates a TC-... id. Prefer stable human-readable ids when the

agent is creating durable catalog tests.

The endpoint can also create several cases at once:

curl -X POST "$SUPABASE_URL/functions/v1/qa-create-test-case" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "testCases": [
      {
        "id": "LYS-A11Y-010",
        "title": "Header navigation exposes visible focus states",
        "area": "Accessibility",
        "priority": "High",
        "expectedResult": "Keyboard focus is visible on every header control.",
        "steps": [
          {
            "action": "Tab through the header navigation.",
            "expected": "Focus follows visual order and remains visible."
          }
        ]
      },
      {
        "id": "LYS-SEO-011",
        "title": "Product pages expose canonical metadata",
        "area": "SEO",
        "priority": "Medium",
        "expectedResult": "Each product page has one canonical URL."
      }
    ]
  }'

Supported fields: projectId, id, title, description, area, browser,

device, status, priority, suiteType (legacy classification), agentGenerated,

strategyNotes, qualityDimensions, tags, screenshots, owner, durationSeconds,

lastRunLabel, release, preconditions, expectedResult, testUrl,

sortOrder, and steps. Each step requires action and expected.

When steps is sent to qa-update-test-case, it replaces the full stored

step list for that test case.

Use status: "Not run" for newly planned or enriched test cases. Do not use

Running when only creating or updating coverage metadata; Running should be

reserved for actual execution state written from a run/result workflow.

Update Test Case

Use this endpoint when the agent needs to attach screenshots or update metadata

on an existing durable test case. This is the correct follow-up after

/api/qa-screenshots; uploading a screenshot stores the file in Blob, while

this endpoint writes the returned metadata onto the test case.

curl -X PATCH "$SUPABASE_URL/functions/v1/qa-update-test-case" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "testCaseId": "POOLIO-AUTH-002",
    "screenshots": [
      {
        "url": "https://...public.blob.vercel-storage.com/qa/project/case/login.png",
        "label": "Poolio login page",
        "targetUrl": "https://poolio.example.com/login",
        "kind": "page",
        "capturedAt": "2026-06-18T10:20:00.000Z",
        "viewport": {
          "width": 1440,
          "height": 900,
          "device": "desktop"
        }
      }
    ]
  }'

Requires test_cases:write. By default, incoming screenshots are appended to

the current screenshots array and deduplicated by url. Send

"replaceScreenshots": true to replace the screenshot list instead.

Supported update fields: projectId, id, testCaseId, title,

description, area, browser, device, status, priority, suiteType (legacy classification),

agentGenerated, strategyNotes, qualityDimensions, tags, screenshots,

replaceScreenshots, owner, durationSeconds, lastRunLabel, release,

preconditions, expectedResult, testUrl, and sortOrder.

Upload QA Screenshot

Use this endpoint when the agent captures a screenshot from any supported test

surface and needs to attach it to QAA. The screenshot surface can be web,

mobile, API-adjacent visual evidence, or a future device/runtime technique. The

endpoint stores the image in Vercel Blob and returns metadata that can be copied

into qa-create-test-case.screenshots, qa-update-test-case.screenshots, or

Application Map node screenshots. The same endpoint also returns an evidence

object. Use that object when a screenshot is evidence for a specific run result

or failed step.

Screenshots are a shared QAA primitive:

- Application Map screenshots describe stable product surfaces.

- Test case screenshots describe expected reference states.

- Run evidence screenshots describe what happened in one execution.

- Mobile screenshots should use the same endpoint when the harness has a safe

image file to upload. Set metadata such as

{"source":"mobile","platform":"android","deviceSerial":"..."}.

curl -X POST "$APP_URL/api/qa-screenshots" \
  -H "Authorization: Bearer qa_agent_..." \
  -F "projectId=PROJECT_UUID" \
  -F "testCaseId=LYS-CHECKOUT-009" \
  -F "runId=run-checkout-regression" \
  -F "stepPosition=2" \
  -F "label=Checkout payment form" \
  -F "targetUrl=https://example.com/checkout" \
  -F "kind=failure" \
  -F "notes=Payment form shows validation error after submit." \
  -F 'viewport={"width":1440,"height":900,"device":"desktop"}' \
  -F 'metadata={"source":"web","technique":"playwright"}' \
  -F "file=@checkout-payment-form.png;type=image/png"

Mobile example:

curl -X POST "$APP_URL/api/qa-screenshots" \
  -H "Authorization: Bearer qa_agent_..." \
  -F "projectId=PROJECT_UUID" \
  -F "testCaseId=MOB-LOGIN-001" \
  -F "runId=run-mobile-login" \
  -F "label=Android login failure" \
  -F "targetUrl=com.example.app" \
  -F "kind=failure" \
  -F "notes=Welcome text did not appear after login." \
  -F 'viewport={"width":1080,"height":2400,"device":"Pixel 8","platform":"android"}' \
  -F 'metadata={"source":"mobile","platform":"android","technique":"adb-screencap"}' \
  -F "file=@android-login-failure.png;type=image/png"

Requires an agent token with test_cases:write. If the token is scoped to one

project, any different projectId is rejected. Maximum file size is 8 MB.

Response shape:

{
  "screenshot": {
    "url": "https://...public.blob.vercel-storage.com/qa/project/case/file.png",
    "label": "Checkout payment form",
    "targetUrl": "https://example.com/checkout",
    "kind": "failure",
    "capturedAt": "2026-06-18T10:20:00.000Z",
    "viewport": {
      "width": 1440,
      "height": 900,
      "device": "desktop"
    },
    "metadata": {
      "source": "web",
      "technique": "playwright"
    }
  },
  "evidence": {
    "url": "https://...public.blob.vercel-storage.com/qa/project/case/file.png",
    "label": "Checkout payment form",
    "targetUrl": "https://example.com/checkout",
    "kind": "failure",
    "capturedAt": "2026-06-18T10:20:00.000Z",
    "runId": "run-checkout-regression",
    "resultId": null,
    "testCaseId": "LYS-CHECKOUT-009",
    "stepPosition": 2,
    "notes": "Payment form shows validation error after submit.",
    "viewport": {
      "width": 1440,
      "height": 900,
      "device": "desktop"
    },
    "metadata": {
      "source": "web",
      "technique": "playwright"
    }
  }
}

Recommended screenshot types:

- For durable test-case screenshots: page, component, or state.

- For run result evidence: before, after, failure, or reference.

When a step fails, prefer uploading a screenshot immediately after the failed

assertion and passing the returned evidence object to qa-add-result. Always

include stepPosition for step-level evidence when a specific step failed.

Public reports render non-sensitive evidence next to the failed step and in the

recent result/run result rows. Mobile evidence is treated as sensitive by

default and should only be shown publicly when a project/report policy explicitly

allows it.

Response shape:

{
  "testCases": [
    {
      "id": "LYS-CHECKOUT-009",
      "project_id": "PROJECT_UUID",
      "title": "Checkout confirms successful card payment",
      "status": "Running",
      "priority": "Critical"
    }
  ],
  "steps": [
    {
      "test_case_id": "LYS-CHECKOUT-009",
      "position": 1,
      "action": "Open checkout with a product in cart.",
      "expected": "Checkout form is visible."
    }
  ]
}

Use this endpoint when the agent discovers missing test coverage, converts a

bug report into a repeatable regression test, or creates planned cases before

running qa-create-run and qa-add-result.

List Test Runs

curl -X POST "$SUPABASE_URL/functions/v1/qa-list-test-runs" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "status": "Failed",
    "includeResults": true,
    "limit": 20
  }'

Requires projects:read. If the token is scoped to one project, projectId is

optional and any other project id is rejected. The endpoint also accepts GET

query parameters, for example:

curl "$SUPABASE_URL/functions/v1/qa-list-test-runs?projectId=PROJECT_UUID&triggerType=AI%20Agent&includeResults=true" \
  -H "Authorization: Bearer qa_agent_..."

Supported filters: projectId, runId, id, status, environment,

triggerType, search, and limit. includeResults defaults to true.

Response shape:

{
  "runs": [
    {
      "id": "run-lysande-public-web",
      "project_id": "PROJECT_UUID",
      "name": "Lysande public web regression",
      "environment": "Production",
      "status": "Failed",
      "trigger_type": "AI Agent",
      "passed_count": 2,
      "failed_count": 1,
      "blocked_count": 1,
      "total_count": 5,
      "scope": ["Homepage", "Lead capture", "SEO"],
      "notes": "Contact form validation failed.",
      "results": [
        {
          "test_case_id": "LYS-CONTACT-006",
          "test_case_title": "Contact form accepts a qualified lead",
          "status": "Failed",
          "notes": "Submission returned validation error."
        }
      ]
    }
  ]
}

Use this endpoint when the agent needs to answer questions such as:

- "Summarize the latest failed run."

- "What failed, passed, or was blocked in the last production run?"

- "Compare recent AI-agent runs for this project."

- "Which test cases repeatedly fail in recent runs?"

Run Plan

Create a run plan before execution. The plan is Sladdis' saved intent: scope,

selected suites/tests, risks, reason, and expected evidence. The endpoint

resolves selected suites and individually selected cases into a deduplicated

selected_test_case_ids snapshot. The later test run links back to this plan

so QAA can compare plan vs actual even when suite membership changes later.

When Sladdis delegates to subagents, include tracks. Tracks are parallel

worker scopes inside the same run plan. Sladdis remains the owner of the final

run, result normalization, dedupe, and QAA persistence.

curl -X POST "$SUPABASE_URL/functions/v1/qa-run-plan" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "title": "Checkout release readiness",
    "runType": "regression",
    "environment": "Preview",
    "scope": ["Checkout", "Authentication", "Payment confirmation"],
    "notInScope": ["Refund admin flows"],
    "riskAreas": ["Payment validation", "Session expiry"],
    "selectedTestSuiteIds": ["CHECKOUT_REGRESSION_SUITE_UUID"],
    "selectedTestCaseIds": ["LYS-CHECKOUT-001", "LYS-AUTH-003"],
    "reason": "Recent checkout changes touched payment and auth handoff.",
    "expectedEvidence": ["Screenshots for failed steps", "Console/network errors when present"],
    "sourcePrompt": "Validate checkout release readiness before deploy.",
    "confidenceBefore": 62,
    "tracks": [
      {
        "name": "UX/UI sweep",
        "agentName": "Sladdis UX Subagent",
        "focusArea": "Navigation, responsive layout, copy, friction",
        "status": "planned",
        "scope": ["Desktop first-pass", "Mobile viewport smoke"],
        "riskAreas": ["Responsive layout", "Primary CTA clarity"],
        "expectedEvidence": ["Screenshots for layout defects"]
      },
      {
        "name": "Accessibility smoke",
        "agentName": "Sladdis A11y Subagent",
        "focusArea": "Keyboard, labels, focus, landmark smoke",
        "status": "planned",
        "scope": ["Keyboard tab order", "Form labels", "Accessible names"],
        "riskAreas": ["Keyboard traps", "Missing labels"],
        "expectedEvidence": ["Failed selector or screenshot evidence"]
      }
    ]
  }'

Requires test_runs:write. The response contains runPlan.id and

runPlan.tracks[] when tracks were saved. Pass the plan id to qa-create-run

as runPlanId.

List recent plans:

curl "$SUPABASE_URL/functions/v1/qa-run-plan?projectId=PROJECT_UUID&status=planned" \
  -H "Authorization: Bearer qa_agent_..."

Update a plan manually when needed:

curl -X PATCH "$SUPABASE_URL/functions/v1/qa-run-plan" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "id": "RUN_PLAN_UUID",
    "status": "completed",
    "actualRunId": "run-...",
    "actualSummary": {
      "status": "Failed",
      "total": 5,
      "passed": 2,
      "failed": 2,
      "blocked": 1,
      "durationSeconds": 420
    }
  }'

Delete an incorrect or obsolete plan draft when it should no longer appear in

Overview. This removes the run plan and its tracks. Existing test runs and test

cases are not deleted; linked runs lose the plan reference.

curl -X DELETE "$SUPABASE_URL/functions/v1/qa-run-plan?projectId=PROJECT_UUID&id=RUN_PLAN_UUID" \
  -H "Authorization: Bearer qa_agent_..."

Update one or more subagent tracks as workers finish:

curl -X PATCH "$SUPABASE_URL/functions/v1/qa-run-plan" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "id": "RUN_PLAN_UUID",
    "tracks": [
      {
        "id": "TRACK_UUID",
        "status": "completed",
        "actualRunId": "run-...",
        "summary": "Desktop navigation passed; mobile CTA wraps awkwardly on 390px.",
        "findings": [
          {
            "severity": "Medium",
            "title": "Mobile CTA wraps into two uneven lines",
            "recommendation": "Shorten the label or widen the button container."
          }
        ],
        "coverage": {
          "viewports": ["1440x900", "390x844"],
          "areas": ["home", "pricing"]
        },
        "confidenceAfter": 76
      }
    ]
  }'

Normally qa-create-run marks the linked plan in_progress, and

qa-update-run marks it completed with an actualSummary when the run

finishes as Passed, Failed, or Blocked.

Create Run

curl -X POST "$SUPABASE_URL/functions/v1/qa-create-run" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "name": "Checkout regression",
    "environment": "Preview",
    "status": "Running",
    "runPlanId": "RUN_PLAN_UUID",
    "runType": "regression",
    "strategy": "Focus on release-critical checkout and authentication paths first.",
    "requestedBy": "agent",
    "sourcePrompt": "Validate checkout release readiness.",
    "startedAt": "2026-06-22T09:16:49.195Z",
    "scope": ["Checkout", "Authentication"]
  }'

Set startedAt to the moment the execution actually begins. The app uses the

run started_at timestamp as the displayed "last run" time for each test case

in that run. If omitted, the API uses the server receive time.

Update Run

curl -X PATCH "$SUPABASE_URL/functions/v1/qa-update-run" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "runId": "run-...",
    "status": "Failed",
    "finishedAt": "2026-06-16T14:00:00.000Z",
    "durationSeconds": 420,
    "notes": "Checkout failed on payment validation."
  }'

Add Result

curl -X POST "$SUPABASE_URL/functions/v1/qa-add-result" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "runId": "run-...",
    "testCaseId": "LYS-CHECKOUT-001",
    "testCaseTitle": "Checkout accepts valid card details",
    "status": "Failed",
    "durationSeconds": 84,
    "failedStepPosition": 2,
    "failureMessage": "Payment form returned a validation error.",
    "notes": "Payment submit returned validation error.",
    "evidence": [
      {
        "url": "https://...public.blob.vercel-storage.com/qa/project/case/file.png",
        "label": "Payment validation failure",
        "targetUrl": "https://example.com/checkout",
        "kind": "failure",
        "capturedAt": "2026-06-18T10:20:00.000Z",
        "stepPosition": 2,
        "notes": "Validation banner remained visible after submitting valid card details.",
        "viewport": {
          "width": 1440,
          "height": 900,
          "device": "desktop"
        }
      }
    ]
  }'

Requires test_results:write. The endpoint writes to the run result log and

also mirrors the result into the test case history used by qa-list-test-cases

and the app's "Recent results" section. If browser or device is omitted,

the endpoint copies those values from the test case. If environment is

omitted, it uses the run environment.

When status is Failed, QAA automatically creates a linked QA Work Board bug

ticket unless one already exists for the same run and test case. The ticket is

created in backlog, has item_type: "bug", qa_status: "failed", links the

test case in linked_test_case_ids, stores the run in qa_run_id, and includes

an agent_prompt repair prompt. The prompt is meant to be copied into a coding

agent: it summarizes the failing test, environment, target, failed step,

evidence labels, reproduction task, and definition of done. Do not create a

second work item for the same failed result unless the first ticket was closed

and the failure is genuinely new.

When a result fails on a specific test step, send failedStepPosition using

the 1-based step number from the test case. Send failureMessage when there is

a concise step-level failure reason. The app uses these fields to highlight the

failed step in the test case detail view and to show the failed step in Recent

results. For credible reports, include evidence screenshots for failed or

blocked results whenever possible. Evidence rows are stored separately from the

durable test case screenshots, so each run can show what happened during that

specific execution.

Response shape:

{
  "result": {
    "id": "RUN_RESULT_UUID",
    "status": "Failed"
  },
  "workItem": {
    "id": "WORK_ITEM_UUID"
  },
  "workItemError": null,
  "autoTicket": {
    "functionVersion": "qa-add-result:auto-ticket-v2",
    "requestedStatus": "Failed",
    "normalizedStatus": "Failed",
    "attempted": true,
    "createdOrReused": true,
    "error": null
  }
}

workItem is null for passed results. If a matching failure ticket already

exists for the same run and test case, or an open bug already exists for the

same test case, workItem.id is the existing ticket id.

If the result is saved but QAA cannot create the Work Board ticket, the endpoint

still returns 201 for the saved result with workItem: null and a non-empty

workItemError. Treat that as an infrastructure issue: record the error in the

run notes or a triage action, then report it to the QAA owner. A common rollout

case is a missing agent_prompt column; QAA will try a fallback ticket without

that column and return a workItemError telling the operator to apply

202606260001_add_work_item_agent_prompt.sql and reload the PostgREST schema.

Use autoTicket.functionVersion to confirm the deployed Edge Function version.

If the field is missing, the endpoint is running an older deploy. If

attempted is false, check the submitted status and normalizedStatus.

When status is Failed or Blocked, QAA also creates or reuses an open

Retest Queue V2 item. The retest is linked to the runId, saved result id,

testCaseId, and the auto-created/reused work item when available. The

response includes retestItem and retestError. Treat this automatic path as

the default. Create a retest item manually only when retestItem is null,

retestError is non-empty, or the unresolved risk did not originate from a

saved Failed or Blocked result.

Create Triage Action

curl -X POST "$SUPABASE_URL/functions/v1/qa-create-triage-action" \
  -H "Authorization: Bearer qa_agent_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "runId": "run-checkout-regression",
    "testCaseId": "LYS-CHECKOUT-001",
    "action": "create_defect",
    "defectTitle": "Checkout rejects valid card details",
    "defectSeverity": "P1",
    "note": "Agent reproduced failure on step 2 with payment validation error."
  }'

Requires test_results:write. Supported actions:

- reviewed

- assign_owner

- mark_flaky

- request_rerun

- create_defect

Use triage actions when the agent has enough evidence to recommend a human

workflow decision. The Work Board bug created or reused by qa-add-result is

the primary failure handoff. Reuse that work item for normal failed-result

follow-up. Use create_defect only when a separate formal defect record is

explicitly needed; it inserts a row in qa_defects and must not be used to

duplicate the existing Work Board bug.

Operational Decision Contract

Use this contract before the detailed loop below:

1. Start from the project brief and choose one QA outcome: verify a handoff,

investigate a failure, close a retest, measure coverage, or support a

release decision. Do not collect unrelated checks into one run.

2. Reuse an existing test case, open defect, work item, and retest whenever they

describe the same behavior. Update the durable record instead of creating a

parallel version of the same problem.

3. A multi-case, coverage, regression, or release run requires a saved run

plan. A single-case linked retest may run without a new plan. It still gets

a new run and result, but keeps traceability to the original failure through

the retest item's source runId, resultId, and testCaseId; omit

runPlanId rather than creating an empty plan.

4. A failed or blocked result requires evidence or an explicit explanation of

why evidence could not be captured. Never present an unsupported failure as

release-blocking certainty.

5. Assess releaseImpact separately from severity. A severe bug can be outside

the active release scope, and a visually small defect can block a critical

flow.

6. Close loops. A passing retest must resolve its retest item and update the

linked Work Board card. A repeated failure must update the existing records

with new evidence and history.

7. Save memory notes only for durable facts. Execution narrative belongs on

the run, actionable failures belong on work items, and future verification

belongs in the retest queue or a next_test note.

8. Finish every run with its decision, unresolved risk, saved QAA links, and one

recommended next QA action.

Recommended Agent Loop

1. Establish the QAA project before doing any durable QA work. Call

qa-list-projects, match the user's URL/request to an existing project,

and reuse its id.

2. If no project matches and the token is workspace-wide, create the project

with qa-create-project immediately. If the token cannot create projects,

ask the user which existing project to use before saving test cases, runs,

defects, workspace data, or agent field notes.

3. Treat the selected or created project id as the anchor for the rest of the

run. Every workspace update, Application Map node, test case, run, result,

triage action, and agent field note should reference this project.

4. Read the project brief with qa-agent-project-brief. Use it as the primary

planning input for release health, coverage gaps, recent failures, active

defects, retests, work items, memory notes, and next-test notes.

5. Read or update the QA workspace with qa-workspace when the brief shows

missing context, missing safe test data, or retest items that need changes.

Use context, testData,

and retestItems as planning input before choosing what to run.

6. Read the QA Work Board with qa-list-work-items. If the user asks the

agent to test queued product work, start from ready_for_qa cards and move

the chosen card to testing with qa-update-work-item.

7. Interpret the user's chat intent and choose a strategy such as smoke,

regression, investigation, coverage, or rerun.

8. Read current product structure with qa-list-app-map when the brief's map

summary is not enough for the task.

9. Read current coverage with qa-list-test-cases and reusable collections

with qa-test-suites when the brief's top tests and coverage gaps are not

enough for the task.

10. If mobile testing is requested, configure safe Android devices and mobile

cases in QAA first. Do not execute arbitrary ADB commands outside the mobile

runner contract.

11. If this is a new or sparse project, bootstrap the project workspace:

upsert project context from the walkthrough, create initial Application Map

application/API/data/coverage layer nodes, create starter smoke test cases,

and save any user-provided test data with the correct sensitivity

classification.

12. During exploration, create or update Application Map nodes and edges with

qa-upsert-app-map-node and qa-upsert-app-map-edge. Use concrete surfaces

such as login page, dashboard, scan form, history table, settings modal,

upload API, database table, customer entity, or payment provider rather than

generic test-case categories.

13. When visual context helps human review, capture a browser or mobile

harness screenshot and upload it with /api/qa-screenshots.

14. Attach relevant screenshots to Application Map nodes and/or durable test

cases. Map screenshots explain what a surface is; test case screenshots

explain the intended page/component/state for that check.

15. Create missing durable coverage with qa-create-test-case, including

quality-oriented tags, strategyNotes, agentGenerated, and uploaded

screenshots. Treat suiteType as legacy compatibility metadata.

16. Update existing durable coverage with qa-update-test-case when screenshots

or metadata need to be attached after creation. Use qa-test-suites to add

cases to reusable named suites; do not encode membership in suiteType.

17. Link each relevant test case to the Application Map with

qa-link-test-case-to-app-node.

18. Before a multi-case, coverage, regression, or release execution, create a

durable run plan with qa-run-plan. Save the selected scope, out-of-scope

areas, selected suite ids and/or individual test case ids, risk areas,

reason, expected evidence,

confidence before the run, and tracks when subagents will execute parallel

UX, accessibility, performance, functional, content, API, or mobile slices.

A single-case linked retest may reuse its existing retest and source-run

context without creating an otherwise empty plan.

19. Start execution with qa-create-run, including runType, strategy,

requestedBy, and sourcePrompt. Include runPlanId when the execution

uses a saved plan; omit it for the single-case linked-retest exception. A

linked plan is marked in_progress automatically.

20. For failed or blocked web steps, capture a Playwright screenshot, upload it with

/api/qa-screenshots, and keep the returned evidence object.

21. For mobile runs, use the mobile runner/harness contract and persist only

redacted mobile evidence. Never paste raw logcat, UI dumps, secrets, or

sensitive screenshots into reports.

22. Add each result with qa-add-result, including failed step metadata and

any uploaded evidence. As subagents finish, patch the matching run-plan

track with status, summary, findings, coverage, and confidence after.

23. Finish the run with qa-update-run. A linked plan is marked completed

and receives an actualSummary when the run finishes as Passed, Failed,

or Blocked.

24. Update the originating Work Board card with qa-update-work-item: use

tested/passed when accepted, in_progress/failed when a bug is found,

or in_progress/blocked with a clear blocked reason when the agent

cannot proceed. done is reserved for accepted/completed work.

25. Create triage actions for failures that need review, defect creation, rerun,

or flaky classification.

26. Persist workspace learnings after the run: update project context when the

product understanding changed, save reusable test data, create retest items

only for unresolved failures that qa-add-result did not already create or

reuse, update resolved retest statuses, and use qa-create-agent-note for

agent field notes that should appear in /agent-notes. Use field notes for

test narrative, reasoning, and project knowledge that is not yet a test

case or finding.

27. Reply in chat with the run summary, saved QAA links, unresolved risks, and

the recommended next test. Do not stop at a chat-only report when QAA access

and a matching project are available.

QAA is the platform where the agent can work with project context, approved

tools, durable QA state, evidence, and triage. Humans talk to the agent, then

watch realtime notifications and the activity timeline, inspect failed runs,

and triage the agent's findings in QAA.

Create Agent Field Notes

Use qa-create-agent-note when Sladdis has useful test narrative that should

appear in /agent-notes and should not overwrite a ticket. Tickets are actions.

Agent field notes preserve observations, reasoning, recommendations, memory,

and next-test ideas. Notes can stand alone or link to a run and/or Work Board

ticket.

qa-create-agent-note creates Agent Field Notes shown in /agent-notes.

curl -X POST "$SUPABASE_FUNCTIONS_URL/qa-create-agent-note" \
  -H "Authorization: Bearer $QAA_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "source": "telegram",
    "noteType": "recommendation",
    "title": "Pure Consulting smoke pass narrative",
    "body": "Summary: tested public website metadata and core navigation.\nObserved duplicate social metadata and broken touch icon.\nNext test: rerun metadata crawler after favicon fix.",
    "tags": ["metadata", "smoke", "public-web"],
    "relatedRunId": "run-...",
    "relatedTicketId": "WORK_ITEM_UUID"
  }'

source must be one of telegram, testbench, qaa_run, manual, or

edge_function. noteType must be one of observation, reasoning,

recommendation, memory, next_test, or reply.

Write notes for scanning and later retrieval:

- Use a short, outcome-oriented title and keep one idea per note.

- Use observation for evidence-backed findings, reasoning for a decision

trace, recommendation for a proposed action, next_test for a concrete

future check, and memory only for durable project facts.

- Link relatedRunId and/or relatedTicketId whenever the note comes from

executable QA work. This keeps the narrative connected to its evidence.

- Prefer three to six stable tags over sentence-like or one-off tags.

- Create replies with noteType: "reply" and the root note's parentNoteId so

follow-up context remains in the same thread.

QA Manager artifacts (QM0)

Use qa-manager-artifacts for structured strategy, risk, coverage, readiness,

gate, metric, and sign-off proposals. This endpoint requires a project-scoped

token with qa_manager:write. Sladdis may write only draft, needs_input, or

ready_for_review; owner/admin approval remains in QAA.

curl -X POST "$SUPABASE_FUNCTIONS_URL/qa-manager-artifacts" \
  -H "Authorization: Bearer $QAA_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_UUID",
    "artifactType": "test_strategy",
    "state": "ready_for_review",
    "idempotencyKey": "strategy-build-abc123-v1",
    "content": {
      "objective": "Protect the release decision",
      "scope": {"in": ["application flow"], "out": []},
      "qualityGoals": ["No critical-path regression"],
      "exitCriteria": ["All release blockers resolved"]
    },
    "sources": [{"kind": "project_brief", "ref": "current"}],
    "evidenceRefs": ["run-abc123"]
  }'

GET accepts projectId and optional artifactType. PATCH requires id,

projectId, and expectedRevision. A stale update returns 409; re-read and

reconcile it. Reusing an idempotency key with a different payload also returns

409. The full schema and responsibility boundary are documented in

docs/qa-manager-contract.md.

Screenshots uploaded through /api/qa-screenshots are signature-validated and

kept in private project-scoped storage. Persist the returned proxy URL and

metadata; never replace it with a raw storage URL.